keep learning blog(キープラーニングブログ)

自分が興味を持ったことを備忘録として残すブログです。

18.Pythonについて紹介します

――物理の原理をバーのウエイトレスに説明できないのであれば、それはウエイトレスではなく、その原理に問題があるのだ。――

アーネスト・ラザフォード

 

 

Pythonについて紹介します

このところ、連続でプログラム関連の記事を書いてきました。おそらく、過去にプログラムを書いた経験のある人であっても、このところの内容は私の専門に近すぎて(ついでに説明が下手すぎて)、いまいちピンとこなかったかもしれません。

もし原子物理学の父ラザフォードの言葉どおりだとすると、私が不十分な説明でこのままブログを書き続けていたら、Pythonという言語に問題があると誤解されてしまいます(なわけない)。ということで、今回はもう少し丁寧に記事を書きます。

f:id:yuki0718:20191119035616j:plain

 

Pythonとは何か

Pythonは、オランダ生まれのプログラマーGuido van Rossum(グイド・ヴァンロッサム)さんが作ったプログラミング言語の名前で、PerlPHPJavaC#といった世の中に数多ある言語のうちの1つです。機械学習畑の人たちがPythonを好んで使用することから、ディープラーニングの流行と比例して注目を集めています(人気言語ランキングでも年々上位にきています)。

Pythonはいわゆるスクリプト言語であり、PC又はサーバーにPythonがインストールされていれば、メモ帳に書いて拡張子を「.py」に変えるだけで手軽に実行することが可能です。他方、C言語のようなコンパイル言語は、書いたプログラムを実行ファイルに変換する「コンパイル」という作業を行って初めて実行できます。

Pythonを自分のPCにインストールするにあたって、Windows PCをお使いの場合には、以前に紹介したAnacondaディストリビューションがおすすめです。以下のWebページに、Anaconda及びPythonのインストール手順が書かれているので、気が向けば誰でもすぐに始めることができます。

tonari-it.com

 

Pythonをインストールしてみよう

Anacondaのインストールが終わったら、画面一番左下にあるWindowsのマークをクリックすると出てくるスタートメニューに、Anaconda Promptという怪しげなソフトが加わっています。

f:id:yuki0718:20191115005907p:plain

Anaconda Promptはコマンドプロンプトとほぼ同じ見た目をしていて、最初にみると厳めしい感じがしますが、慣れると「黒くて目に優しいなあ」という気持ちになります。

AnacondaにはPythonの主要なライブラリが同梱されているので、インストールしたらすぐに色々なプログラムを書ける状態になっています。このライブラリ(Library)というのは、私がよくプログラムの先頭に書いている、

import numpy as np

に出現する「numpy」のようなツールボックス名のことです。この一文は、「このプログラムではnumpyというライブラリを利用可能にして、呼び出すときは略してnpと呼びます」という宣言です。以後、プログラム中で「np.関数名」のように入力すれば、numpyライブラリから便利な行列計算用ツールを呼び出すことができ、やりたいことを効率的に実現できます。

世の中には数多のライブラリが存在しており、プロ制作からアマチュア制作まで玉石混交です。新しいライブラリを自分のPCにインストールしたい場合には、Anaconda Promptのコマンドラインに「conda install ライブラリ名」と入力してEnterキーを送信するだけで簡単に導入することができます*1

また、既にライブラリが入っている場合に、それを最新バージョンにアップデートしたいときは「conda update ライブラリ名」と入力します。ときどき特殊なインストール方法が必要なこともあるので、「Anaconda ライブラリ名 インストール方法」のようなキーワードでGoogle検索しましょう。

 

Pythonファイルを作ってみよう

Pythonスクリプト言語なので、メモ帳にプログラムコードを書いて拡張子を「.py」に変えればすぐに実行可能だとご説明しました。しかし、多くのプログラマーは、コードを効率的に書けて、かつ誤りの検証がしやすい専用のソフト(エディターと呼びます)を使っています。

実は、Anacondaをインストールしておけば、Pythonを書くことに特化したエディタ「jupyter notebook」が標準インストールされています。立ち上げ方は非常に簡単です。先ほどのAnaconda Promptのコマンドラインに「jupyter notebook」と半角スペースで区切って入力し、Enterキーを送信するだけです。以下のような画面が立ち上がると思います。

f:id:yuki0718:20191116020251p:plain

本来のjupyter notebookは白くてクリーンな画面ですが、私は色合いが暗いと目に優しい(気がする)ので、色をダークモード風にカスタマイズしています。私と同じような雰囲気が好きな方向けに、以下のWebページで色のカスタマイズ方法が説明されています。説明はとても分かりやすいので参考になさってください。

qiita.com

さて、今回はDocumentフォルダ中に「Test」というフォルダを作りました。クリック操作でその中に入ると以下のような画面になります。

f:id:yuki0718:20191116022622p:plain

ご覧のとおり、右上の「new」というタブから出現したリストの中に、「Python3」とあります。これをクリックすれば、このフォルダ中にjupyter notebook形式のファイル(拡張子「.ipynb」のファイル)が作成されます。

f:id:yuki0718:20191116024338p:plain

これがエディタの編集画面です。「セル」と呼ばれる箱の中に、普通のメモ帳と同じ感覚でPythonのコードが書けます。試しにセル内に「あいうえおかきくけこ」とイタズラ書きしてみました。Notebookという名前のとおり、このエディタは実験用ノートとしても使うことができます。左上の方にある「+」ボタンをクリックすればセルを追加できるので、イタズラ書きのノート部分とプログラムの本体部分とを分けられます。

さて、jupyter notebookの使いやすいところは、Pythonのコードを書いたらその場で実行して、バグがないか簡易チェックできる点にあります。コードの書かれたセルがアクティブな状態で「▶Run」ボタンをクリックすると、その時点で書かれているコードが実行されます(実行中に書き換えても意味なし)。

このように、jupyter notebookはそのプラットフォーム上でコードを書くことと実行することを同時に行えるので、効率的にPythonプログラムの開発が行える素晴らしいエディタです。私はデンマークに来てから一日何時間もこのエディタとにらめっこして怪しいコードを書いたり実行したりしている毎日です。

ただし、注意点として、拡張子「.ipynb」のファイルはjupyter notebook上でしか実行できません。別のPCで動かさなければならない場合などは、書かれている内容を拡張子「.py」のファイルにコピペすればAnaconda Prompt経由でPythonを動かすことができます*2

どうしても「.py」形式のファイルが必要で、かつ上述したコピペ作業が面倒だなあという方は、新しくセルを作って以下のようなコードを実行すると、notebookと同じフォルダ中にPythonファイルが自動生成されます。

import subprocess
subprocess.run(['jupyter', 'nbconvert', '--to', 'python', 'ファイル名.ipynb'])

 

Pythonの文法を勉強してみよう

けっこういろんな方から(研究室の同僚にも)「あなたは専門が物理なのにどうやってプログラミングの勉強をしたんですか?」と聞かれます。しかし、プログラミングは勉強というより単なる慣れです。漢字の書き取りと一緒です。私はインターネットで「Python テキストファイル読み込み」みたいにGoogle検索をかけてコピペしたりしながら、その文法(Syntax)に慣れていきました*3

大学時代はセメスターごとに物理の教科書を数冊読むのが当たり前で、それがまた徐々に深遠な自然科学の世界に入り込んでいく感じがして楽しかったのですが、プログラミングの教則本は退屈であまり読む気がしません。普通自動車免許のルール本並みに退屈です。

そのため、私の場合は本を数日間で流し読みして、どこに何を書いているのか掴んでからは辞典みたいな使い方をしています。つまり、知りたいことがあるときに開いて調べるくらいで、そこまで真剣に読んでいません。その程度の理解でよろしければ、私のおすすめは以下の本です。

基礎 Python (IMPRESS KISO SERIES)

基礎 Python (IMPRESS KISO SERIES)

また、機械学習のうちディープラーニングに特化したプラットフォームとして、GoogleのTensorFlow(企業向け)とFacebookのPytorch(研究者向け)という二種類が存在しています。TensorFlowで機械学習をやりたい場合には以下の本が断然おすすめです。

[第2版]Python 機械学習プログラミング 達人データサイエンティストによる理論と実践 (impress top gear)

[第2版]Python 機械学習プログラミング 達人データサイエンティストによる理論と実践 (impress top gear)

この本の著者Sebastian Raschkaさんは、データサイエンティストという統計データ処理のプロフェッショナルです。非常に分かりやすく、初心者の気持ちを分かってくれている記述なので、読み進めるごとに機械学習の基礎が身についていく感覚がありました。英語の第1版なら著者のホームページで無料ダウンロードできたと記憶しています。

 

Pythonコードの書き方(備忘録)

本なんて偉そうに紹介したものの、私もプログラミングを始めたのは社会人になってからで、Pythonに至っては数年前にちょっと触って最近ようやく本格的に始めた程度の「超にわか」です。

日々、基本的な文法を覚えてはスポーツカー並みのスピード感で忘れていくので、備忘録としていろいろ書き残してみます。

if flag == 0:
    #何か文字をAnaconda Promptやjupyter notebook画面に出力
    print('0の場合の処理')

elif flag == 1:
    #何もしないで通過
    pass

else:
    #flagが0又は1以外の場合
    '何かの処理'

まずは最も基本的な論理構造である場合分け(if文)を使い、文字列をprint関数でAnaconda Promptやjupyter notebookの画面に出力するコードです。

基本的に、Pythonではfor文やif文のような処理のひとまとまりを括弧{ }で括ることはしません。Pythonでは代わりに「:」という記号の後の字下げによって段落を作り、視覚的に処理をひとまとまりにします。字下げにはtab記号を使うか、4個のスペース記号を使うのが一般的です(私は断然スペース記号派です)。

 

hairetu = []
for i in range(10):
    hairetu.append(str(i) + '番目だよ')

続いて、コンピュータお得意の単純繰り返し(for文)と配列への追加処理です。プログラミング言語には配列と呼ばれる番号(Index)付きの箱みたいな変数が用意されており、Pythonには[ ]で表される「リスト」が標準で使えます。

上述したコードでは、まずhairetuという安直な名前のリストを用意して、リストのおしりに「~番目だよ」という文字列データを格納(Append)する動作をfor文で繰り返しています。リストに格納したデータを呼び出すときは、「hairetu[7]」のように記述すれば番号7のデータを呼び出せます(ただし番号は必ずfloat型ではなくint型つまり小数7.0じゃなく整数7じゃないとダメ)。

なお、「range(回数)」はイテレータIterator)という繰り返し用の変数です。range(10)はi=0~i=9までを1刻みで繰り返します。最初が0から始まるので最後は9で終わるのがなかなかトリッキーです。他の言語では1から始まることもあるので、常に0始まりであることを意識しましょう。

 

file_path = 'C:/Users/ユーザー名/Documents/Test/ファイル名.txt'
#ファイル読み込み
with open(file_path, 'r') as f:
    data = f.read().rstrip()
    hairetu = data.split('\n')

#ファイル書き込み
with open(file_path, 'w') as f:
    f.write('何かの書き込みたいテキスト' + '\n')

お次はテキストファイルの読み込みと書き込みです。with openというまとまりを作ると、そのまとまりが終わったタイミングでテキストファイルを閉じてくれるので、「f.close()」みたいなファイルを閉じる処理を書かずに済みます。

また、ファイル読み込みのところで「rstrip()」とあるのは、テキストファイル最終行の改行記号(\n)を除去する関数です。大抵テキストファイルは改行記号(\n)で区切られてデータが収まっており(じゃないと横長で見づらい)、各行のデータを行番号ごとの配列に格納したいとき、「データ.split(区切り文字)」という関数で一気にリスト変換するのが普通だからです。

もしも最終行のラストに改行記号(\n)が残っていると、splitによって得られるリストのおしりに何も存在しない空白文字が格納されてしまうので、「rstrip()」によって邪魔な改行を除去する必要があるのです。

 

### My function ###
def zibun_func(i):
    #適当な数学計算(引数iに1を足して2乗、その値を4倍して2で割る)
    a = (i + 1) ** 2
    b = a * 4 / 2
    
    #関数から外に出てくる戻り値としてbを指定
    return b

### Main Part ###
if __name__ == "__main__":
    result = []
    for i in range(5):
        #自分関数を呼び出して結果をresultリストに逐次格納
        temp = zibun_func(i)
        result.append(temp)
    
    #自分関数の計算結果の一番目だけをAnaconda Promptやjupyter notebook画面に出力
    print(result[0])

自分でオリジナルの関数を定義するためには、上述したように「def 関数名(引数1, 引数2):」という形でまとめます。関数を呼び出すメイン部分は「if __name__ == "__main__":」という形でまとめます。

Pythonファイルは、実行すると必ず最初に「if __name__ == "__main__":」以下の部分にあるまとまりを実行する仕組みになっています(この記述がない場合は頭から実行)。しかし、多くのプログラマーは複雑な処理をすべてメイン部分に書き込むことはせず、処理の塊ごとに自分で関数(Function)を定義して小分けにし、それを呼び出す形をとります。

上述したコードだと、zibun_funcという名前のオリジナル関数を上部で先に定義して、メイン部分のfor文の中で繰り返しその関数を呼び出しています。関数は処理が終わって得られた結果を外に出力することができ、何を外に出すかは「return 出したい変数」によって指定します。何も外に出さない場合は「return」だけで大丈夫です。

今回は適当な数学計算をして結果を外に出しました。それをメイン部分の側で「result」というリスト変数にappendで格納します。つまり、resultというリストにはオリジナル関数の数学計算の結果が順次格納されていきます。全部の格納が終わったら、とりあえず一番目「result[0]」だけ画面に出力しています。

 

### Imports library ###
%matplotlib inline
import matplotlib.pyplot as plt

### Main Part ###
if __name__ == "__main__":
    #グラフに表示したいデータを適当に用意(今回は一次関数)
    x_list = [1, 4, 7, 10, 13]
    for i, x in enumerate(x_list)
        print(str(i) + '番目のxで計算しているよ~')
        y = 2 * x - 1
    
    #リストに格納されたデータx, yを二次元散布図でグラフ表示
    plt.rcParams["font.size"] = 14
    plt.figure(figsize=(16, 5))
    plt.plot(x, y)
    plt.title('なんかグラフタイトル')
    plt.xlabel('なんかx軸の名前')
    plt.ylabel('なんかy軸の名前')
    plt.show()

Pythonでグラフを描きたい場合には、「matplotlib.pyplot」というライブラリがおすすめです。私のようにjupyter notebookで開発する場合はグラフを下部に表示させるおまじない「%matplotlib inline」も一緒に書いておきましょう。

上述したコードでは、一次関数を散布図グラフでプロットしています。まずはリストの形で適当にxの値「x_list」を用意し、for文で「y=2x-1」という一次関数を計算します。実はPythonでは「range()」によるイテレータだけでなく、リスト自体も直接イテレータとして使えるのですが、上述のように「enumerate(リスト)」という便利な関数を使うと、リスト番号「i」とリストの中身「x = x_list[i]」の両方をfor文中で取り出せます*4

さて、肝心のグラフ表示について、標準フォントでは小さすぎて見づらいので、私はいつも「plt.rcParams["font.size"] = 14」と最初にフォントサイズを大きくして見やすくしています。「plt.figure(figsize=(16, 5))」は横16×縦5のサイズでグラフ描画領域を定義し、「plt.plot(x, y)」でリスト形式のxとyの値を散布図としてプロットします。後はお好みでラベルを付けたり軸に名前を付けたりしています。

 

### Import library###
import numpy as np

### Main Part ###
if __name__ == "__main__":
    #マトリョーシカのようにリストにリストを格納して行列を作る
    matrix = []
    lines = [1, 2, 3, 4]
    for i in range(4):
        matrix.append(lines)
    
    #行列を行列計算に特化したnumpy配列形式に変換
    matrix = np.array(matrix)
    
    #行列の各成分に1を足して各成分を二乗して各成分の平方根をとる(適当)
    matrix = matrix + 1
    matrix = matrix ** 2
    matrix = np.sqrt(matrix)
    
    #行列の成分同士を掛け算(行列の積ではなく要素積またはアダマール積と呼ばれるやつ)
    matrix2 = matrix * matrix
    
    #行列同士を行列積の規則で掛け算
    result = matrix2 @ matrix
    #result = np.dot(matrix2, matrix)と書いても同じ結果
    
    #他にも全成分が0または1の行列や対角成分のみ1の単位行列等も作れる
    zero_matrix = np.zeros((4, 4))
    one_matrix = np.ones((4, 4))
    unit_matrix = np.identity(4)
    
    #行列同士を行(axis=0)や列(axis=1)に沿って結合して新しい行列を作れる
    gyo_ketugo = np.concatenate((matrix2, matrix), axis=0)
    retu_ketugo = np.concatenate((matrix2, matrix), axis=1)
    
    #行列式を計算したり逆行列を求めるのもお手のもの
    determinant = np.linalg.det(matrix)
    inverse_matrix = np.linalg.inv(matrix) #行列が大きいとめちゃ時間かかる

機械学習ではよく行列やテンソルの掛け算で効率的にプログラムを組むので、そうした計算を高速で実行できる「numpy」は非常に便利なライブラリです。上では私がよく使う、一度リストで作ってからnumpy形式に変換して行列を作るやり方や、行列の各成分に一律同じ演算を行うやり方、要素積と行列積のやり方、特殊な行列の作り方、行列同士の結合などを一通りまとめました。

ただし、線形代数学を大学で勉強された方ならお分かりのとおり、要素積(アダマール積)を行う場合には両者の行列のサイズが同一でないとエラーになりますし、行列積を行う場合には左側の行列の列数と右側の行列の行数が同一でないとエラーになります。よって、常に目の前のnumpy配列がどんなサイズの行列なのかコメントなどで書き残した方が得策です。

 

### Import library###
import joblib as job
import numpy as np

### Main Part ###
if __name__ == "__main__":
    ## リストの保存・読出し方法 ##
    #リストの入れ子で適当に行列を作る
    matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    
    #リストを3倍圧縮してテキストファイル形式で保存
    file_path = 'C:/Users/ユーザー名/Documents/Test/ファイル名.txt'
    with open(file_path, 'wb') as f
        job.dump(matrix, f, compress=3)
    
    #ローカルに保存したテキストファイルからリストを読込み
    file_path = 'C:/Users/ユーザー名/Documents/Test/ファイル名.txt'
    with open(file_path, 'rb') as f
        matrix = job.load(f)
    
    ## numpy配列の保存・読出し方法 ##
    #numpy配列形式の行列を作る(この時点でメモリ上の容量が圧縮される)
    matrix = np.array(matrix)
    
    #numpy配列はsaveすると「.npy」という拡張子のファイルに保存される
    np.save('C:/Users/ユーザー名/Documents/Test/ファイル名', matrix)
    
    #ローカルに保存した「.npy」ファイルからnumpy配列を読込む
    matrix = np.load('C:/Users/ユーザー名/Documents/Test/ファイル名.npy') #拡張子「.npy」を忘れずに

10GBを超える巨大な行列やテンソルを扱うときなど、結果をローカルに保存しておきたいときがあります。上述のとおり、リストとnumpy配列とで保存・読出し方法が異なります。一般的には、numpyの方が効率的にメモリを使えるので、普通はnumpy配列の形式にするのが(必要に応じて保存するのが)推奨されます。

なお、マニアックな話になりますが、あえてnumpy配列の弱点を挙げるとしたら、それはnumpy配列では各軸の要素数が同一でないとエラーになってしまうという点です。他方、リストは自由な入れ子構造が可能であるため、一行目の要素数が4で二行目の要素数が2、みたいなことも許容されます。普通はそんな非対称な行列(?)を扱うことはほぼないため、大人しくnumpy配列を使います。

 

### Import library###
import glob as gl

### Main Part ###
if __name__ == "__main__":
    #特定のフォルダーに存在するすべての「.txt」ファイルを取得
    main_path = 'C:/Users/ユーザー名/Documents/Test/*.txt'
    files = gl.glob(main_path)
    
    #すべてのファイルについて繰り返し
    hairetu = []
    for file in files:
        #ファイルごとに中身を読み出して配列に格納していく
        with open(file, 'r') as f
            s = f.read().rstrip()
            hairetu.append(s)

「glob」というライブラリを使えば、特定のフォルダ内に存在するすべてのファイル名を取得して、順次処理することが可能です。私の場合は「.wav」形式という音声データのファイルを一つのフォルダに格納して、順次特徴量ベクトルに変換する処理を行うとき、これを使いました。上述のコードで「*.txt」のところを「*.wav」に書き換えれば私の用途で使えます。

 

### Import library###
import math

### Main Part ###
if __name__ == "__main__":
    #適当な数学計算の繰り返し回数を定義
    N = 100
    
    #プログレスバー用変数の用意
    unit = math.floor(N/20) #進捗度は20段階に
    bar = "#" + " " * math.floor(N/unit)
    
    #N回の繰り返し処理
    for n in range(N):
        #何らかの適当な数学計算
        a = (n * 3) / 2
        
        #for文の進捗状況を示すプログレスバーの表示
        print("\r[{0}] {1}/{2} Processing...".format(bar, n+1, N), end="")
        if n % unit == 0:
            bar = "#" * math.ceil(n/unit) + " " * math.floor((N-n)/unit)
            print("\r[{0}] {1}/{2} Processing...".format(bar, n+1, N), end="")
    
    print()

長大な計算を行う機械学習等で、for文を回している間にプログラムが本当にちゃんと動いているのか不安になったり、いつ頃終わるのか読めなくてイライラしたりすることがあります。そういうときには上述したような簡易プログレスバーを作って常に進捗状況を出力するのがおすすめです。

「math」というライブラリは平方根や四捨五入などの基本的な数学計算用の標準ライブラリです。多くの場合はnumpyがあるのでmathは使いません。しかし、今回のように小数点以下を切り捨てて整数にする「math.floor」という関数を使うことはよくあります。

実はnumpyにも「numpy.floor」という同じ機能の小数切り捨て関数があるものの、numpyで計算すると結果が自動的に64bit浮動小数点float形式になってしまうので、リストの番号として使いたい場合などに私はこっちを使っています(前述したとおりリストの番号指定は整数int型しか受けつけない)。

なお、上述したコードから分かるように、プログレスバーを次の行に改行せずに上書きし続けるには、「\r」というキャリッジリターンを頭に付けると実現可能です。printの中に書かれたformat関数は、変数を{0}と{1}の位置に表示するよう出力形式を整えて出力するためのものです。例えば、「a=0.1, b=-5」のときに「print('{0}あいうえお{1}'.format(a, b))」と書くと、「0.1あいうえお-5」と出力されます。

 

以上、今回はPythonというプログラミング言語を始めるときのおすすめ本や基本文法をご紹介しました。機械学習以外でも、Pythonのようなスクリプト言語を使って効率化できるお仕事・業務はたくさんあるので、Excelしか使ったことのない方もお試しでPythonを使ってみることをおすすめします。

稚文をお読みいただきありがとうございました。

*1:ネットでライブラリのインストール方法を調べていると「pip ライブラリ名」というインストール方法が掲載されていることがありますが、Anacondaを利用している方にはおすすめしません。condaとpipは混ぜるな危険と言われており、競合するとライブラリ同士が混線を起こして再インストールする羽目になります。私は一度なりました。

*2:Anaconda Promptのコマンドライン上に「python フォルダパス/ファイル名.py」と入力してEnterキーを送信すればPythonファイルを実行できます。どう考えてもjupyter notebookの方がお手軽です。

*3:英語は10年以上やってるのに未だに慣れません。なんで?

*4:なお、番号(インデックス)iは整数型intなので、文字列と一緒にprintで出力するときは「str(i)」という型変換をしないとエラーになります。私はこの手の馬鹿げたエラーをデンマークに来てから10回はやりました。