🐕

公開データセットをダウンロードして使える形にする

2021/01/13に公開

こんにちは、竹輪内からしです。
公開データセットで遊ぼうと思ったら、普段はTensorflowについているデータセットを使います。
そうすると、違うタスク(例えば、ドメイン適応)をやってみようかと思ったときに、
データがなくて困ってしまうときがあります。
そこで、今回は、備忘録のために、データセットの加工について書いていきたいと思います。

対象

  • 久々に公開データセットを拾って来ようと思ったが、やり方を忘れてしまった将来の自分
  • 公開データセットを拾って来ようと思った人

以下では、基本的にlinuxコマンドを使って操作します。
また、python3を使ってコーディングしていきます。
※手順4以降は画像データセットを使うことを想定して書きます。
※筆者はjupyter notebookで対話的にチェックしながらコードを書いている少々ため、くどい書き方になっているかもしれません。ご容赦下さい。

0. ライブラリを確認する

昨今では、様々なデータセットが公開されています
scikit-learnのサンプルデータセット
pytorchのサンプルデータセット(画像)
pytorchのサンプルデータセット(音)
pytorchのサンプルデータセット(テキスト)
tensorflowのサンプルデータセット

ここになかったら頑張ってググるなりしてデータセットがダウンロードできるwebページを見つけます。

1. ファイルをダウンロードする

linuxマシンにターミナル入力でダウンロードします。
(ダウンロードボタンをクリックしてダウンロードでもよいですが、ここではlinuxコマンドで書きます。)

以降、ないシェルコマンド(以下の例ではhogeというコマンドがない場合)は
ターミナルで以下のように入力して取ってきましょう。

sudo apt-get install hoge

もしくは

sudo yum install hoge

WEBからファイルを持ってくる有名なコマンドに

  • wget
  • curl

があります。

筆者はなんとなくいつもwgetを使っていますが、これを機に違いを見てみました。
参考:curlとWgetの比較
なるほど、サポートするプロトコルが大きく違うんですね。

それでは、フォルダを指定してダウンロードしましょう。
この例ではファイル(https://www.hoge.com/dataset.zip)をフォルダ(/home/datasets)に保存します。

wget -P /home/datasets https://www.hoge.com/dataset.zip
curl -o /home/datasets https://www.hoge.com/dataset.zip

2. ファイルを転送する

このステップは不要ならなくてもよいですが、
プロキシの都合等で計算機サーバに直接ダウンロードできない場合は
手元でダウンロードしてサーバに転送します。

scpコマンドを使えばsshで転送できます。
scpコマンドの使い方を簡単に見てみましょう。
ここでの設定は

  • カレントフォルダにあるファイルhoge.zipを
  • 転送先(tensousaki.com)のアカウント(karashi.chikuwa)のフォルダ(/home/huga/)に転送する

という例で書きます。(もちろんですが、.zip以外も送れます。)

scp ./hoge.zip karashi.chikuwa@tensousaki.com:/home/huga/

もし、hoge.zipを解凍してできたフォルダhogeを送る場合には以下のように-rオプションを指定します。

scp -r ./hoge karashi.chikuwa@tensousaki.com:/home/huga/

解凍は次に書きます。
データセットのようなサイズの大きいフォルダを送るよりは圧縮したデータを送ったほうがいいような気がします。

3. ファイルを解凍する

公開データセットはzipやtarで圧縮されている場合が多いので解凍します。
ここでは、データセット(dataset.zipまたはdataset.tar.gz)を解凍先フォルダ(/path/hoge)に解凍します。

.zipの場合は以下のようになります。

unzip dataset.zip -d /path/hoge

.tar.gzの場合は以下のようになります。

tar xvzf dataset.tar.gz -C /path/hoge

調べていて知ったのですが、
unarというコマンドは色々な圧縮ファイル(ZIPやTARだけでなく7zやLZHなど)を解凍できるみたいですね。
unarを使うと以下のようになります。

unar -o path/hoge dataset.zip

4. データセットを処理できる形にする

筆者はtensorflowユーザなので(Num, H, W, Ch)の形のnumpyファイルがあると嬉しいです。
そのため、ここでは、画像を(Num, H, W, Ch)の形にすることを目標とします。
(Num:データ数、H・W:画像のサイズ、Ch:チャネル数)
(Channel firstのデータが欲しい場合は適宜読み替えてください。)

以降では、持っていないライブラリ(例ではhoge)があれば、
ターミナルに以下のように入力するか、

pip3 install hoge

Jupyter Notebook上で以下のように入力して取ってきましょう。

!pip3 install hoge

※以降では、画像の正規化はしませんが、一般的には以下のように8bit画像を正規化します。
こうすることで画素値が[0,1]の範囲に収まり、扱いやすくなります。

import numpy as np

images = images.astype(np.float32) / 255 # 255 = 2^8 - 1で8bitの最大値を表す
# code

※以降では、画像とラベルの配列を入手することを目的としていますが、
以下のように、pickelして保存してもよいでしょう。

import numpy as np

np.savez('dataset.npz', images = images, labels = labels)

"""
# ちなみに読むときは
dataset = np.load('dataset.npz')
images = dataset['images']
labels = dataset['labels']
# のようにします。
"""

4.1 ダウンロードしたファイルが.matだったとき

(ここではSyn. Digitsという数字の画像データセットを例に挙げて紹介します。)
.matというのはMATLABで用いられるファイル形式です。
MATLABユーザだったら非常に嬉しいところですが、Pythonユーザはどうでしょうか?
ここであきらめなければならないのか....
scipyが使えればあきらめる必要はありません!

まずは、.matファイルを読みます。

import scipy.io

dictionary = scipy.io.loadmat(filename)

では、早速中身を確認します。

print(dictionary)

'X'というのが画像データで'y'というのがラベルデータのようです。
ここで、画像データの次元も確認してみましょう。

print(dictionary['X'].shape)
print(dictionary['y'].shape)

しかし、画像データが(W, H, Ch, Num)の形のため、tensorflowユーザの筆者には扱いにくいです。

とりあえず、Numを先頭に持ってくるように変換しましょう。ここでは転置を使いました。

images = dictionary['X'].T

こうすると、(Num, Ch, W, H)になります。
筆者としてはChが最後の次元にあってほしいため、もうひと手間かけます。

images = np.swapaxes(images, 1, 3)

こうすることで、次元が入れ替わって(Num, H, W, Ch)になります。
※念のため、書いておきますが、pythonの番号は0始まりです。

まとめ(Syn. Digitsを読む関数)

関数にまとめておきます。

import scipy.io

def load_syndigit(filename):
	dictionary = scipy.io.loadmat(filename) # .matファイルの読み込み
	images = np.swapaxes(dictionary['X'].T, 1,3) # 画像データの次元を整える
	labels = dictionary['y'] # ラベルデータを取得
	return images, labels	

4.2 ダウンロードしたデータセットが画像ファイル+テキストファイルだった時

(ここでは、MNIST-mという数字の画像データセットを例に挙げて紹介します。)

テキストファイルはpythonの標準の関数で読みます。
画像ファイルの読み方は色々ありますが、ここでは筆者がよくインストールを間違えるPILを使おうと思います。
PILについてはインストール方法を書いておきます。
↓ターミナル

pip3 install pillow

↓Jupyter Notebook上

!pip3 install pillow


まずはテキストファイルを読んで、1行だけ見てみましょう。

f = open(filename) # テキストファイルを開く
data = f.read() # データを取ってくる 
f.close # ファイルを閉じるのを忘れない
print(data)

ファイル名 ラベル (改行)

といった形のデータみたいですね。
画像を読むために、前半と後半を分けたいです。

f = open(filename) # テキストファイルを開く
data = f.read().split('\n') # データを取ってきて1行ずつをリストに入れる
f.close # ファイルを閉じるのを忘れない
sample = data[0] # 1行目だけ取ってくる
filename, label = sample.split() # スペースで行を分割する

これでファイル名が取ってこれたので、画像を読みたいと思います。

from PIL import Image
import matplotlib as plt
# % matplotlib inline # jupyter notebookで表示させる用のメモのためコメントアウト
import numpy as np

# pathは画像フォルダのパスです。
img = Image.open(path+filename) # 画像の読み込み
img_array = np.asarray(img) # numpy配列が扱いやすいのでnuppy配列に変換

plt.show(img_array, cmp='gray_r') # 画像の表示

ここで、画像とラベルをそれぞれ1つの配列にまとめます。
ここでは、下のようにまとめました。

images = np.concatenate([images, np.expand_dims(img, axis = 0)], axis = 0)
labels = np.concatenate([labels, np.expand_dims(label, axis = 0)], axis = 0)

まとめ(MNIST-mを読む関数)

関数にまとめておきます。

import numpy as np
from PIL import Image

def load_mnist_m(filename):
	image_path, _ = filename.rsplit('_', 1) 
	# 解凍したらファイル名~_labels.txtの~部分が画像フォルダだったので
	
	# テキストファイル読み込み
	f = open(filename)
	data = f.read().split('\n')
	f.close
	
	# データセットの形にしていく
	for i in range (len(data)):
		sample = data[i]
		
		if not sample:
			break # 最後の行が空白だった
			
		else:
			# 画像の読み込み
			file, label = sample.split()
			filename = image_path + '/' + file
			img = Image.open(filename)
			img = np.asarray(img)
			
			# labelはstr型よりもint型で扱いたい
			label = np.array([int(label)]) 
			
			# 連結
			if i == 0:
				imeges = np.expand_dims(img, axis = 0)
				labels = np.expand_dims(label, axis = 0) 
				# (Num, 1)という形が個人的に好みだったので
			else:
				images = np.concatenate([images, np.expand_dims(img, axis = 0)], axis = 0)
				labels = np.concatenate([labels, np.expand_dims(label, axis = 0)], axis = 0) 
				# (Num, 1)という形が個人的に好みだったので
	return images, labels	

4.3 ダウンロードしたデータセットがクラス毎にフォルダ分けされた中に画像データが入っていた時

(ここでは、not MNISTという数字の画像データセットを例に挙げて紹介します。)
このような場合はフォルダ内のフォルダ名・ファイル名を取得する必要があります。

まずはフォルダ(folder)の中身のリストを取得する方法を書きます。

import os

file_list = os.listdir(folder)

"""
# ちなみにフォルダの中身があるかを確認するには
os.path.isdir(folder)
# とします。
# また、ファイルサイズを確認するには
file_size = os.stat(folder).st_size
# とします。
"""

次に、フォルダの名前を確認する方法を書きます。
この例では、フォルダ名のアルファベットがラベル名に対応すします。
この例ではフォルダ(folder)名のアルファベットをクラスを表す数字(0~9)に変換します。

import os

# フォルダ名を取ってくる
label_name = os.path.basename(folder) 
# アルファベットと数字を対応させる
alpha2num = {a: i for a, i in zip('ABCDEFGHIJ', range(10))}
label = alpha2num[label_name]

あとは、ファイルリストに含まれているファイル名を読んで画像ファイルを開き、
4.2に書いたように画像とラベルをそれぞれ1つにまとめるだけです。

まとめ(not MNISTを読む関数)

関数にまとめておきます。

from PIL import Image
import numpy as np
import os

def load_not_mnist(foldername):
	# ラベルを数字に対応させておく
	alpha2num = {a: i for a, i in zip('ABCDEFGHIJ', range(10))}
	images = None
	labels = None
	cnt = 0
	
	for root_dir in [foldername]:
		folders = [os.path.join(root_dir, d) for d in sorted(os.listdir(root_dir))
			  if os.path.isdir(os.path.join(root_dir, d)) ]

		for folder in folders:
			# フォルダの名前を取ってくる
			label_name = os.path.basename(folder) 
			label = np.array([alpha2num[label_name]])
					
			# フォルダ内の画像ファイルの名前のリストを取得
			for file in os.listdir(folder):
				# サイズが0のファイルはスキップする
				if os.stat(os.path.join(folder, file)).st_size != 0:
					try:
						img = Image.open(os.path.join(folder, file))
						img = np.asarray(img)
						if cnt == 0:
							imeges = np.expand_dims(img, axis = 0)
							labels = np.expand_dims(label, axis = 0) 
						else:
							images = np.concatenate([images, np.expand_dims(img, axis = 0)], axis = 0)
							labels = np.concatenate([labels, np.expand_dims(label, axis = 0)], axis = 0) 
						cnt += 1
					except:
						continue
				else:
					continue
	return images, labels
		

参考

Discussion