読者です 読者をやめる 読者になる 読者になる

100 numpy exercisesの解説 1~50

NumPyでよく使うテクニックが網羅されたnumpy-100/100 Numpy exercises.ipynb at master · rougier/numpy-100 · GitHubを一通りやってみました。以下はその途中で調べたメモ書きです。断らない限り64bit Python 3.6 + numpy 1.11で試しています。

問題数が多いので、この記事では50問目までを対象とします。

20. Consider a (6,7,8) shape array, what is the index (x,y,z) of the 100th element?

unravelは、「〈もつれた糸・編み物などを〉解く」という意味の動詞です(〈もつれた糸・編み物などを〉解く)。

26. What is the output of the following script?

python組み込みのsum()関数は、sum(iterable[, start])という引数を持ちます。これは以下のように計算されます。(参考:python sum function - `start` parameter explanation required - Stack Overflow)

def sum(values, start = 0):
    total = start
    for value in values:
        total = total + value
    return total

なので、print(sum(range(5),-1))を計算すると -1 + 0 + 1 + 2 + 3 + 4 = 9となります。

一方、from numpy as npすると、python組み込みのsum()関数の代わりに、np.sum()が呼ばれます。np.sum()の引数をnumpy.sum — NumPy v1.12 Manual で調べると、この問題の最終行は

print(np.sum(range(5), axis=-1))

と等価であることがわかります。

axisは何番目の軸を指定するかを意味します。負の値をいれた場合は、軸を逆順で数えます。今回の場合、range(5)は1次元なので、-1の指定は0の指定と同じです。結局、0 + 1 + 2 + 3 + 4 = 10となります。

27. Consider an integer vector Z, which of these expressions are legal?

Z <- Zが最初理解不能でした。<-という演算子があるわけではなく、Z < -Zをわざと変なスタイルで書いているだけだと思います。

>>> a = np.array([0,-3,4])
>>> a <- a
array([False,  True, False], dtype=bool)

28. What are the result of the following expressions?

まずnp.array(0)が理解できませんでした。np.array([0])であれば長さ1のベクトルですが、np.array(0)とは何なのでしょうか?

実はnp.array(0)は「0という値を持つ0次元のarray」を表すようです。コンソールで値をいじくってみると性質をつかめると思います。(参考:numpy - error extracting element from an array. python - Stack Overflow

>>> a = np.array(7)
>>> type(a)
<class 'numpy.ndarray'>

# 普通のndarrayが持つ関数はすべて使える
>>> a.ndim
0
>>> a.dtype
dtype('int64')
>>> a.min(), a.max()
(7, 7)

# 0次元なのでインデックスでアクセスはできない
>>> a[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: too many indices for array

# item()関数を使うと値を取り出せる
>>> a.item()
7
>>> type(a.item())
<class 'int'>

0次元のarrayというのは数学的にはスカラーだと思うのですが、NumPyにおける0次元のndarrayは、NumPyにおけるscalarとは別物のようです。

>>> np.isscalar(np.array(0))
False
>>> np.isscalar(np.int_(4))
True

この問題に関しては python - Why are 0d arrays in Numpy not considered scalar? - Stack Overflow に詳しいですが、自分は深入りしていません。0-dimension array問題 - Qiita にも0次元のndarrayに関する詳細があります。

以上を踏まえて、28. を試してみます。実はPython 3とPython 2で挙動が変わります。まずはPython 3.6の場合。

>>> print(np.array(0) / np.array(0))
__main__:1: RuntimeWarning: invalid value encountered in true_divide
nan
>>> print(np.array(0) // np.array(0))
__main__:1: RuntimeWarning: divide by zero encountered in floor_divide
0

このRuntimeWarningは初回実行時のみ表示されるもので、同じ演算を2回目以降行っても表示されません。

次にPython 2.7 + numpy 1.21で試した場合。from __future__ import divisionは設定していません。

>>> print(np.array(0) / np.array(0))
__main__:1: RuntimeWarning: divide by zero encountered in divide
0
>>> print(np.array(0) // np.array(0))
__main__:1: RuntimeWarning: divide by zero encountered in floor_divide
0

ちょっと自分には理解不能な結果です。

3つ目はPython2, Python3ともに以下の結果になりました。

>>> print(np.array([np.nan]).astype(int).astype(float))
[ -9.22337204e+18]

np.float64型であるNaNをnp.int64型として解釈し、再びnp.float64型として解釈するとNaNに戻らないことを示した例なのですが、残念ながら何が起こっているのかここに書けるほどのことは分かりませんでした。

29. How to round away from zero a float array ?

“round away from zero”, つまり0から遠い方向に小数点を丸める方法について問うています。例えば、2.2は3に丸め、-7.4は-8に丸めることになります。 いったんndarrayの要素をすべて正の数にして、np.ceil()で0から遠い方向に丸めたあと、copysign()で元の符号を復活させています。

32. Is the following expressions true?

np.sqrt(-1)nanになり、np.emath.sqrt(-1)1jになります。ここで、jは複素数を表す記号です。np.emath複素数を扱うときに使うようですが、emathが何の略かはわかりませんでした。

38. Consider a generator function that generates 10 integers and use it to build an array

np.fromiter()は、イテレータからndarrayを生成する関数です。引数のcountは、要素数の上限です。

41. How to sum a small array faster than np.sum?

実際に試してみました。確かにnp.add.reduce()のほうがnp.sum()より高速です。

# setupで指定した部分は最初の1回のみ実行される。最初の引数は100万回実行される。
>>> timeit.timeit("np.add.reduce(a)", setup="import numpy as np; a = np.arange(10);")
0.7812885560124414
>>> timeit.timeit("np.sum(a)", setup="import numpy as np; a = np.arange(10);")
1.9236456239887048

python - What is the difference between np.sum and np.add.reduce? - Stack Overflow によると、``np.sum()は内部でnp.add.reduce()を呼んでいるので、オーバーヘッド分だけnp.sum()のほうが遅くなるようです。

43. Make an array immutable (read-only)

writableではなくwriteableなのに注意。

44. Consider a random 10x2 matrix representing cartesian coordinates, convert them to polar coordinates

R = np.sqrt(X**2+Y**2)の部分はR = np.hypot(X, Y)としたほうが簡潔です。

47. Given two arrays, X and Y, construct the Cauchy matrix C (Cij =1/(xi - yj))

C = 1.0 / np.subtract.outer(X, Y)とありますが、実際は添字の順序を考えるとC = 1.0 / np.subtract.outer(Y, X)が正しいような気がします(違っていたらすみません)。

このouterという関数は、ベクトルAに含まれる要素aと、ベクトルBに含まれる要素bとの全ペアに対して演算を施すときに使う関数のようです。(参照:numpy.ufunc.outer — NumPy v1.12 Manual

Pythonのitertoolsでできる全列挙のまとめ

Pythonitertoolsモジュールには、イテレーションに関する便利関数が多数用意されています。この記事では、その中でも競技プログラミングで全列挙に使える関数についてまとめます。Python 2.x, 3.xのどちらでも使えます。

Google Code JamのSmallでは全列挙するだけで解ける問題が出ることが多いです。一通りどんな関数があるか知っておきましょう。

import文

スクリプトの冒頭に import itertoolsと書いてあるものとします。

1, 2, 3と書かれた玉が入った袋から、2つの玉を取り出す (3P2の順列)

nums = [1, 2, 3]
for balls in itertools.permutations(nums, 2):
    print(balls)

結果は以下です。

(1, 2)
(1, 3)
(2, 1)
(2, 3)
(3, 1)
(3, 2)

ここで、第2引数を省略すると3P3 (= 3!)が列挙されます。C++std::next_permutation()でできることと同じです。

nums = [1, 2, 3]
for balls in itertools.permutations(nums):
    print(balls)

結果は以下です。

(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)

1, 2, 3と書かれた玉が入った袋から、2つの玉を区別なく同時に取り出す (3C2の組み合わせ)

nums = [1, 2, 3]
for balls in itertools.combinations(nums, 2):
    print(balls)

結果は以下です。

(1, 2)
(1, 3)
(2, 3)

1, 2, 3と書かれた玉が入った袋から、2つの玉を取り出す。ただし取り出した玉はまた袋に戻す (重複順列)

nums = [1, 2, 3]
for balls in itertools.product(nums, repeat=2):
    print(balls)

結果は以下です。

(1, 1)
(1, 2)
(1, 3)
(2, 1)
(2, 2)
(2, 3)
(3, 1)
(3, 2)
(3, 3)

1, 2, 3と書かれた玉が2セット入った袋から、2つの玉を区別なく同時に取り出す(重複組合せ)

nums = [1, 2, 3]
for balls in itertools.combinations_with_replacement(nums, 2):
    print(balls)

結果は以下です。

(1, 1)
(1, 2)
(1, 3)
(2, 2)
(2, 3)
(3, 3)

2つの集合の直積をとる

signs = ['a', 'b', 'c']
nums = [1, 2, 3]
for pairs in itertools.product(signs, nums):
    print(pairs)

結果は以下です。

('a', 1)
('a', 2)
('a', 3)
('b', 1)
('b', 2)
('b', 3)
('c', 1)
('c', 2)
('c', 3)

kaggleの練習コンペDogs vs. Catsに出場

Kaggleで開催されていた練習用コンテスト Dogs vs. Cats Redux: Kernels Edition | Kaggle に参加しました。このコンテストでは、画像の中に犬または猫のどちらが映っているかを識別する識別器を学習し、その性能を競います。通常では犬か猫かの2値で答える問題設定が多いかと思いますが、このコンペでは犬か猫かの確率値を0.0から1.0の間の実数値で答えます。

結果は1314人中74位で、上位6%に入ったといえば聞こえはいいのですが、実態は手も足も出なかったという印象です。以下、簡単なメモです。

実装1: Chainer

まずはChainerを使ってDeepNetの自力実装を試みました。ディープラーニングの様々なモデルを使ってCIFAR-10画像データセットの分類を行う - Qiita のVGG likeなモデルを参考に、与えられた学習データのみを使ってスクラッチからのモデルの学習を試みました。

入力画像をGTX 1070のメモリが許す限り大きくしたり(144x144の画像からランダムな位置で128x128の画像を切り出し)、on-the-flyで画像に摂動を加えて水増ししたり、出力層をsigmoid cross entropyにして確率値を出せるようにしたりなど、思いつきそうな工夫をあれこれ入れました。しかしエラー値0.13(正解率にして95%程度、順位にして約50%のあたり)程度で足踏みしてしまいました。感覚的にはもう少しよい値が出そうだと感じましたが、これがネットワークの限界なのか、学習方法に何らかの不備があるのかは不明です。

実装2: Keras

Kaggleではコンペ中も掲示板形式で参加者同士の議論が公開されています。その中に、Kerasの70行ほどのコードでエラー値0.06を出すサンプルが掲載されていました(Dogs vs. Cats Redux: Kernels Edition | Kaggle)。このコードでは、あらかじめ学習済のネットワークを特徴量抽出器として流用し、犬猫判別の判別器部分のみを学習する、いわゆる転移学習が行われています。

このコードを使うと、あっさりとエラー値0.059が出ました。転移学習恐るべしです。さらに小手先の閾値調整(値の範囲をクリッピングして、誤分類時のペナルティを下げるなど)を実施して、エラー値が0.05164(正解率にして99%程度)まで下がりました。

感想

学習データが数万枚オーダーで存在したためスクラッチで学習してもそれなりの精度が出ることを期待しましたが、実際は転移学習によるサンプルコードの性能に遥か及びませんでした。

今回はほぼサンプルコードを動かしただけで終わってしまいましたが、次回は最低一つくらいは何らかの工夫を加えてサンプルコードよりよい成績を狙いたいと思います。また、Discussionをくまなくチェックし、素早く巨人の肩に乗る練習も行いたいと思います。

自作PCにUbuntu 16.10をインストール

Core i7 7700 + GTX 1070 なPCを自作 - minus9d’s diary の続き。自作したPCにUbuntu 16.10をインストールしました。初物ハードウェアということでインストールがうまくいくか不安で、実際トラブルもありましたが、今のところ順調に動いています。

USBメディアの作成

今回はEtcher by resin.ioを使いました。

Ubuntu 16.10のインストールとトラブル

作成したUSBからブートしてUbuntu 16.10をSSDにインストールしました。インストール自体は問題なく終了しましたが、マウスポインタが画面の左上に固定されて動かないトラブルが発生しました。

GPUのドライバーを入れると直るという記事を読んだため、nVidiaの公式から最新のドライバー (現時点で NVIDIA-Linux-x86_64-375.26.run)を取得し、drivers - How to install NVIDIA.run? - Ask Ubuntu に従いドライバーのインストールを試みました。

しかし、途中で ERROR: The Nouveau kernel driver is currently in use by your system. This driver is incompatible with the NVIDIA driver, and must be disabled before proceeding. Please consult the NVIDIA driver README and your Linux distribution's documentation for details on how to correctly disable the Nouveau kernel driver. というエラーが出てインストールに失敗します。どうやらUbuntuにプリインストールされているNouveauというOSSのドライバーと競合しているようです。

そこで、nvidia - How do I disable the “Nouveau Kernel Driver”? - Ask Ubuntu に従い、sudo update-initramfs -u を行ってからNVIDIA-Linux-x86_64-375.26.runを実行することで、ドライバーのインストールに成功しました。他の回答にある /etc/modprobe.d/nvidia-graphics-drivers.confファイルの手修正は、自分の場合は不要でした。

ドライバーを入れたあとは、マウスカーソルを自由に動かせるようになりました。LogicoolのマラソンマウスM705(Logicool独自のUnifyingを使用)も、ドングルを刺すだけで使えています。

nvidia cuda toolkitのインストール

cudaのインストールに関しては Install GPU TensorFlow From Sources w/ Ubuntu 16.04 and Cuda 8.0 – Justin’s Blog が参考になります。

CUDA 8.0 Downloads | NVIDIA Developer から、Ubuntu 16.04向けのrunファイルをダウンロードしました。単にsudo sh cuda_8.0.61_375.26_linux.runとするとError: unsupported compiler: 6.2.0. Use --override to override this check.というエラーが出てインストールに失敗したため、sudo sh cuda_8.0.61_375.26_linux.run --overrideとしてインストールしました。本当に問題がないかは不明です。

実際、pipでtensorflow-gpuとkerasを入れた後、keras/examples/mnist_cnn.pyを実行すると、3エポック目の途中のおそらくほぼ同じ場所でOSごと落ちるという現象を確認しています。これが原因かは不明です。

CapsをCtrlに変更

keyboard - How do I remap the Caps Lock and Ctrl keys? - Ask Ubuntu に従い、gnome-tweak-tool を使います。CapsとCtrlの入れ替えも試しましたが、terminalでCtrl + Shift + Tabによるタブ追加が効かなくなったので、Capsを潰してCtrlにしました。

# ホームディレクトリのディレクトリを英語にする

Ubuntuでホームディレクトリの中身を英語にする - Qiita に従います。

(追記) Ubuntu 16.10は新しすぎる?

実は今ではもうUbuntu 16.10は使っていません。デフォルトのgccが6系と新しいためにビルド関係でいろいろ苦労することが多かったためです。やはり16.04や14.04の方が情報も多く安心できると思います。

Core i7 7700 + GTX 1070 なPCを自作

2010年に購入し、パーツを継ぎ足しつつ愛用していたDellXPS 8100。もはやベンチマークの比較対象にならなくなったCore i7の第一世代を積んだPCで、ちょっとした開発程度なら問題はないものの、本格的な開発をするには限界を感じ始めたため、一念発起してPCを新調することにしました。

BTOストアで購入した場合のシミュレーションをコスパ最強GTX1070搭載のおすすめゲーミングBTO PCを徹底比較! : 自作とゲームと趣味の日々を参考に行ったのですが、理想的な構成に変更すると思いの外高価になったため、初の自作を試みるに至りました。

パーツの選定

獲得したポイント分を差し引いて14万円強の出費でした。以下にその内訳を記します。購入金額はすべて税込です。

CPU: Intel Core i7 7700

オーバークロックありの7700kとなしの7700で迷いましたが、初の自作でオーバークロックはリスキーだと考え7700を選択しました。

マザーボード: ASUS STRIX H270F GAMING

自作だとASUSが定番だと聞いていたので、ASUSで決め打ちにして検討を開始しました。

まずチップセットの違いを【Kaby Lake】Z270, H270, B250チップセットの違いについてで学びました。Z270はH270やB250と異なりCPUのオーバークロックnVidiaGPUのSLIを可能にしますが、いずれも自分が行う可能性は低いため、H270のマザボから選ぶことにしました。

最初はPRIME H270-PROを買う予定だったのですが、途中でROG STRIX H270F GAMINGに変えました。"GAMING"と名のつくマザーボードは、GPUの刺し口である PCI Express 3.0 x16がSafeSlotと呼ばれる機構で補強されています。そのため、重量級のGPUを刺した時の耐久性が増しているようです。どこまで神経質になるべきかは分かりませんが。

メモリ: Crucial DDR4-2400

メモリはCrucialの8GB * 2です。

パソコン工房はCPU、マザーボードとメモリの3点セットを販売しています。パソコン工房 楽天市場店で購入(【楽天市場】[お買い得3点セット]Intel Core i7 7700+DDR4-2400 8GB×2枚+ASUS STRIX H270F GAMING 3点セット:パソコン工房 楽天市場店)し、67,020円 + ポイント6%でした。

GPU: ASUS ROG STRIX-GTX1070-8G-GAMING

TSUKUMOの店頭で5万円を切っていたので購入。48,078円でした。GTX1070-O8Gモデルよりも若干クロック数が劣ります。大きい分ファンが3個付いており静かです。

ストレージ: Crucial SSD MX300 525GB M.2 Type CT525MX300SSD4

最近のマザボにはM.2と呼ばれるスロットがあり、ここにSSDを装着できると聞き、付けてみることにしました。

Amazon.co.jpで16,696円でした。購入してから気付いたのですが、M.2スロットに挿して使うSSDには、SATAタイプとPCIeタイプの2種類があり、PCIeタイプのほうが高速だそうです (M.2 SSD は SATA? PCI Express? ソケットの種類)。私は値段だけ見て適当に買ってしまったので、SATAタイプのものを買ってしまいました。PCIeタイプのほうがSATAタイプのものよりかなり高額とはいえ、せっかくなのでPCIeタイプを買って最先端の速度を体験したかった気もします。しかし、PC内の配線が2本いらなくなるというメリットは享受できており、一定の効果は感じます。

ところで、私が購入したH270F GAMINGは2つのM.2スロットを備えますが、そのうち1つはSATAタイプとPCIeタイプの両方を受け付けるものの、もう1つはPCIeタイプのものしか受け付けません。購入の際には注意が必要です。

電源: Corsair CX650M 80PLUS BRONZE

価格.comの売れ筋ランキング1位のものを選びました。ヨドバシカメラで7,820円 + ポイント10%でした。

パソコンケース: SAMA 黒鉄

価格.comの売れ筋ランキング1位の価格.com - SAMA 黒透 JAX-02W 価格比較でいいかと思ったのですが、側面がアクリルで透けて見えるのは落ち着かなさそうだったので、サイドパネルが非アクリルである JAX-02 黒鉄 kurotetsu | パソコン工房【公式通販】 を購入しました。パソコン工房で3,980円でした。

無線LAN 子機: BUFFALO Air Station WLI-UC-G301N

LinuxのUSB無線LAN子機の対応状況 - Qiita で、Linuxでの動作実績を調べ、手軽な値段の本品を購入しました。実際、ドライバのインストールをすることなく、刺すだけで認識してくれました。Amazon.co.jpで1,267円でした。

OS: なし

自分を追い込むためOSは買いませんでした。そのうち日和ってWindowsを買うかもしれません。

組み立て

パソコンケースに説明書がなく組み立てに苦労しましたが、画像検索でこのケースを使っている人を見つけてなんとか組み立てできました。3時間くらいかかったと思います。

マザーボードの上部にある電源コネクタ(EATX12V)へ電源線が届かずどうしようかと思いましたが、電源から真上方向に線を這わせることで接続できました。光学ドライブも省略し、M.2 SSDを採用したため配線は必要最小限で、これならMicro-ATXを選択するのもありだったかなと思います。

M.2 SSDの装着時には、マザーボードに付属するスペーサーを使って底上げをする必要があります。最初このことをしらず、スペーサー無しで接続し、通電してしまっていました。

windows 10 + cuda + chainer で corecrt.h が見つからない問題

Windowsに正式対応していないchainerをwindowsで動かす際のエラーについてメモします。

環境

現象

GPUを利用するchainerのコードを実行すると、以下のようにcuda絡みのエラーが出て実行に失敗しました。

  File "C:\Users\(ユーザ名)\Anaconda3\lib\site-packages\chainer\initializers\normal.py", line 31, in __call__
    loc=0.0, scale=self.scale, size=array.shape)
  File "cupy/core/core.pyx", line 1260, in cupy.core.core.ndarray.__setitem__ (cupy\core\core.cpp:24263)
  File "cupy/core/core.pyx", line 2336, in cupy.core.core._scatter_op (cupy\core\core.cpp:59948)
  File "cupy/core/core.pyx", line 1496, in cupy.core.core.elementwise_copy (cupy\core\core.cpp:51768)
  File "cupy/core/elementwise.pxi", line 774, in cupy.core.core.ufunc.__call__ (cupy\core\core.cpp:42682)
  File "cupy/util.pyx", line 37, in cupy.util.memoize.decorator.ret (cupy\util.cpp:1283)
  File "cupy/core/elementwise.pxi", line 582, in cupy.core.core._get_ufunc_kernel (cupy\core\core.cpp:39296)
  File "cupy/core/elementwise.pxi", line 32, in cupy.core.core._get_simple_elementwise_kernel (cupy\core\core.cpp:29818)
  File "cupy/core/carray.pxi", line 87, in cupy.core.core.compile_with_cache (cupy\core\core.cpp:29505)
  File "C:\Users\(ユーザ名)\Anaconda3\lib\site-packages\cupy\cuda\compiler.py", line 131, in compile_with_cache
    base = _empty_file_preprocess_cache[env] = preprocess('', options)
  File "C:\Users\(ユーザ名)\Anaconda3\lib\site-packages\cupy\cuda\compiler.py", line 94, in preprocess
    pp_src = _run_nvcc(cmd, root_dir)
  File "C:\Users\(ユーザ名)\Anaconda3\lib\site-packages\cupy\cuda\compiler.py", line 56, in _run_nvcc
    raise RuntimeError(msg)
RuntimeError: `nvcc` command returns non-zero exit status.
command: ['nvcc', '--preprocess', '-Xcompiler', '/wd 4819', '-m64', 'C:\\Users\\(ユーザ名)\\AppData\\Local\\Temp\\tmp_c64g0d4\\kern.cu']
return-code: 2
stdout/stderr:
b'kern.cu\r\n#line 1

...(中略)...

#line 320 "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/../../VC/INCLUDE\\\\vcruntime.h"\r\n#line 10 "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/../../VC/INCLUDE\\\\crtdefs.h"\r\nC:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/../../VC/INCLUDE\\crtdefs.h(10): fatal error C1083: include (略) \'corecrt.h\':No such file or directory\r\nnvcc warning : The \'compute_20\', \'sm_20\', and \'sm_21\' architectures are deprecated, and may be removed in a future release (Use -Wno-deprecated-gpu-targets to suppress warning).\r\n'

試したこと1

corecrt.hが見つからないというエラー文をヒントに検索したところ、 Qtでcorecrt.hが見つからないと言われる問題 - あくまで個人的メモ用ブログ に「「ユニバーサルWindowsアプリ開発ツール」をインストールすることで直った」という報告が見つかりました。

しかし、残念ながら私の環境では直りませんでした。

試したこと2

「開発者コマンドプロンプト for VS2015」を起動し、このコマンドプロンプトを使ってpythonスクリプトを実行することで、上記エラーが出なくなることを確認しました。

「開発者コマンドプロンプト for VS2015」にてecho %INCLUDE%すると、corecrt.hを含むディレクトリであるC:\Program Files (x86)\Windows Kits\10\include\10.0.14393.0\ucrt;が登録されていることが分かります。このおかげのように思われますが、自信はありません。

pythonのsplit関数を使って空白文字で分割するときの注意点

pythonのsplit関数を使うと、文字列をある文字で分割することができます。

例えば、カンマで文字列を分割するには以下のようにします。

s = "a,b,c"
print(s.split(','))  # ['a', 'b', 'c']

s.split()s.split(' ')は同じ出力となると勘違いしていましたが、実際は以下のように挙動が異なるので注意が必要です。

s = "a b  c"
print(s.split())    # ['a', 'b', 'c']
print(s.split(' ')) # ['a', 'b', '', 'c']

s.split()では連続する空白をひとまとめに扱いますが、s.split(' ')は連続する空白をそれぞれ別々に分割していることが分かります。 python 2.7とpython 3.5で動作確認をしました。