【PyTorch】DataLoaderで(images, labels)だけでなく(images, labels, paths)をロードできるようにする
まずglob.glob()を用いて, すべての画像データのパスを保持したリストを作る.
def make_datapath_list(phase="train"): rootpath = "./data/" target_path = os.path.join(rootpath, phase, "**", "*.jpg") # 最初の**はクラスのディレクトリ path_list = [] # globを利用してサブディレクトリまでファイルパスを格納 for path in glob.glob(target_path): path_list.append(path) return path_list # 動作確認 train_list = make_datapath_list(phase="train")
次に前処理クラスを作っておく.
前処理クラスはImage.open("path")で読み込んだ1画像配列を受け取り, 変形などをしtorch.tensorを返すクラス
class ImageTransform(): def __init__(self, resize, mean, std): self.data_transform = transforms.Composee([transforms.ToTensor(), transforms.Normalize(mean, std)]) def __call__(self, img): return self.data_transform(img)
次にDatasetクラスを作成する. この際に, __getitem__()メソッドで返す値を3要素のタプルにする.
class Dataset(data.Dataset): def __init__(slef, file_list, transform=None): self.file_list = file_lsit self.file_transform = transform # 前処理クラスのインスタンス def __len__(self): return len(slef.file_list) def __getitem__(self, index): img_path = self.file_list[index] # index番目のpath img = Image.open(img_path) # index番目の画像ロード img_transformed = slef.transform(img) # 前処理クラスでtensorに変換 label = img_path.split("/")[2] # クラスを表すディレクトリの文字列 label = convert(label) # クラスを表すディレクトリ文字列から数値に変更 return img_transformed, label, img_path train_dataset = Dataset(file_list=train_list = make_datapath_list(phase="train"), transform=ImageTransform(size, mean, std))
データローダを作成
batch_size = 32 train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True) # 動作確認: イテレータを作って初めのやつを表示する tmp = iter(train_dataloader) inputs, labels, paths = next(tmp) print(inputs.size()) # torch.Size([32, 3, h, w]) print(labels) # tensor([label0, ...., label31]) print(paths) # [file_path0, ..., file_path31]
尚、動作の保証なし
Datasetクラスの__getitem__を工夫すれば, 良いことがわかった.
例えば, 2つの画像の組をロードすることや, 1つの画像とそれをセグメンテーションモデルにかけたもう1つのマスク画像の対をロードすることなど出来そう
UbuntuでLoLをプレイ
【TeX】独立記号を自作してみた
LaTeXでグラフィカルモデルなどで用いる_||_という記号の定義
\newcommand{\indepe}{\mathop{\perp\!\!\!\perp}} \newcommand{\notindepe}{\mathop{\perp\!\!\!\!\!\!/\!\!\!\!\!\!\perp}}
物体認識を試す
ImageNetにおける物体認識をしたいとする.
通常ならイチからモデルを組み, データを入手し, 学習させ……という作業が必要なわけだが, ディープラーニングのフレームワークKearsならこれが簡単にできる.
さらに, VGGやResNet, Xception, MobileNetなど自宅の計算機スペックでは学習が難しそうなモデルでも予め用意されたパラメータを取得することで, 研究所並の精度の予測ができる.
import numpy as np from keras.applications.vgg16 import VGG16 from keras.applications.vgg16 import preprocess_input, decode_predictions import keras.preprocessing.image as Image # モデルの宣言 model = VGG16(weights="imagenet")
続いて予測を行う.
画像は次の画像を用いる.
# 予測 image_path = "angora.png" image = Image.load_img(image_path, target_size=(224, 224)) # ImageNetのサイズ x = Image.img_to_array(image) x = np.expand_dims(x, axis=0) # 次元を追加。(224, 224)->(1, 224, 224) x = preprocess_input(x) # ImageNetでやってる前処理を同じようにやる result = model.predict(x) result = decode_predictions(result, top=3)[0] print(result) # show description
結果は以下の通り「この画像がアンゴラである確率は99.99%以上」スゴイッ!!
[('n02328150', 'Angora', 0.9999628), ('n02123394', 'Persian_cat', 1.3111491e-05), ('n02111889', 'Samoyed', 1.070684e-05)]
k-NN法をフルスクラッチ実装
今日はk-NN法(k-NearestNeighbor)を実装する.
k-NN法の概念
k-NN法は教師データから未知データを予測する教師あり学習の一つで, また, 線形回帰などのようにパラメータを最適化するような手法を取らない.
すなわち, 「教師データから識別境界のようなものを学習してから未知データが境界のどちらにあるか」という手法をとらずに, 「それぞれの未知データに対して教師データとの関係を見てどちらのクラスかを予測する」という方法をとる. これを怠惰学習というらしい.
k-NN法に関しては, 名前の通り, 未知データに対して最も近い教師データk個のうち多い方のクラスラベルを予測クラスとする.
まず下図のようなデータがあるとする. 赤色がクラス1, 緑色がクラス0 とする. 星がクラスラベルのわかっていない未知データである.
この未知データがどちらのクラスに入れた方が良いのかを予測したい.
今k=3すると未知データから最も近い3つの教師データを抽出できる.赤色(クラス1)のデータが1つで緑色(クラス0)のデータが2つである.
よって多数決をとって, 緑(クラス0)データの方が多いので, 未知データは緑(クラス0)と予想する.
式で表すとこのようになるが, わざわざ式で表さなくてもよい.
データの生成
k-NN法を実装するために二次元のデータを生成する.
k-NN法は分布を仮定した方法ではないため, 分布から生成する必要は無いと思うが, ラベル付きのデータを生成したかったので取り敢えずガウス分布を使ってデータを生成した.
import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 乱数固定 np.random.seed(0) # 平均ベクトル Mu1, Mu2 = np.array([1, 10]), np.array([9,2]) # 分散共分散行列 sigma11, sigma12, sigma21, sigma22 = 4, 5, 6, 3 SIGMA1, SIGMA2 = np.array([[sigma11**2, np.sqrt(sigma11*sigma12)],[np.sqrt(sigma11*sigma12), sigma12**2]]), np.array([[sigma21**2, np.sqrt(sigma21*sigma22)],[np.sqrt(sigma21*sigma22), sigma22**2]]) # データ生成 values1 = np.random.multivariate_normal(Mu1, SIGMA1, 200) values2 = np.random.multivariate_normal(Mu2, SIGMA2, 200) # データ保存 np.save("./data/values1.npy", values1) np.save("./data/values2.npy", values2) # 散乱図の表示 plt.scatter(values1[:,0], values1[:,1], color="r") plt.scatter(values2[:,0], values2[:,1], color="g") plt.xlabel("x") plt.ylabel("y") plt.xlim(-10, 30) plt.ylim(-10, 25) plt.savefig("./img/data.png") plt.show()
k-NN法による未知データの分類
格子点を未知データとして考え領域分割を行う.
格子点から各データ点までの距離を計算し, 最も近いデータ点k個のラベルから多数決を行う.
閾値が0.5のとき, kが奇数の場合は多数決が成立するのだが, kが偶数の場合で各ラベルが同じ個数あるとき多数決が成立しない.
よってkは奇数にするほうが良いことが分かる.
またkが大きくなると, 汎用性が高くなることが分かる.
import numpy as np import matplotlib.pyplot as plt # k-NN法のパラメータkを設定 k = 200 # データのロード values1 = np.load("./data/values1.npy") values2 = np.load("./data/values2.npy") # データを # 統合, その際に教師情報もつける data = np.vstack((values1, values2)) labels = np.hstack((np.ones(len(values1)), np.zeros(len(values2)))) # 未知のデータとして格子点を作成 # 2つのデータから最小, 最大のx, yを探す x_min, x_max = min(np.min(values1[:,0]), np.min(values2[:,0])), max(np.max(values1[:,0]), np.max(values2[:,0])) y_min, y_max = min(np.min(values1[:,1]), np.min(values2[:,1])), max(np.max(values1[:,1]), np.max(values2[:,1])) # 格子点を作成 xx = np.linspace(x_min-1, x_max+1, 300) yy = np.linspace(y_min-1, y_max+1, 300) xxx, yyy = np.meshgrid(xx, yy)# 格子点(xxx, yyy)が生成された. これを未知データとみなしてk-NN法を適用する. class0 = [] class1 = [] thresholds = [] # k-NN法の適用 for i, (xx, yy) in enumerate(zip(xxx, yyy)): for j, (x, y) in enumerate(zip(xx, yy)): # 未知データ(x,y)から既知データdata各点への距離を計算する # 距離はユークリッド距離の二乗, すなわちl2ノルムの二乗を計算する tmp = data - (x, y) distances = np.linalg.norm(tmp, axis=1) # 最も小さいスコア上位k点のラベルを参照 supervisers_index = np.argsort(distances)[:k] # print(supervisers_index) supervisers = labels[supervisers_index] # print(supervisers) res = np.sum(supervisers)/ k # if res > 0.5 then x_res in class1 if res > 0.5: class1.append((x, y)) elif res == 0.5: thresholds.append((x, y)) else: class0.append((x, y)) class0, class1, thresholds = np.array(class0), np.array(class1), np.array(thresholds) plt.scatter(values1[:,0], values1[:,1], color="r", marker="x", s=20) plt.scatter(values2[:,0], values2[:,1], color="g", marker="x", s=20) plt.scatter(class1[:,0], class1[:,1], color="r", marker="o", s=1, alpha=0.1) plt.scatter(class0[:,0], class0[:,1], color="g", marker="o", s=1, alpha=0.1) try:# 奇数の場合, 必ず多数決が成立するので閾値(0.5)とイコールになるものがないためエラー処理しておく plt.scatter(thresholds[:,0], thresholds[:,1], color="b", s=1, alpha=0.1) except: pass plt.title("./img/{}-NN methods".format(k)) plt.xlabel("x") plt.ylabel("y") plt.xlim(-9, 23) plt.ylim(-7, 23) plt.savefig("./img/{}-NN.png".format(k)) plt.show()
最小二乗法を実装する
予測モデルをつくるときに最も簡単な方法としては「最小二乗法による線形モデル」と「k近傍モデル」である.
この2つは統計学や機械学習を学んでいない人でも割と思いつきそうな方法である.
線形モデルとはパラメータに対して線形なモデルという意味である.
例えば, y = ax + b という1次関数を中学で学んだ.
今, データxが観測されたときに, yを予測するというタスクを行う.
このときa(傾き), b(切片)の値によって予想するべき値yが異なってくる.
すなわちa, bが決定されれば全てのxの入力に対してyという出力ができるようになる.
このようなa, b をパラメータという.
これを多次元に拡張すると
これは
と見て
とベクトル表記で書くことができる.
これは, p+1 次元の一つのデータに対する予測の式なので, n個のデータを同時に扱う場合には
とする. は(p+1, n)行列であり転置すると(n, p+1)行列, パラメータベクトルは(p+1)ベクトルである. ((p+1, 1)行列という表現もできる)
この線形モデルを, 最小二乗法によりフィットする. (予測と実際の誤差を最小にする)
この式はパラメータに関して二次式であるので最小値が存在する.
パラメータで微分してになるパラメータを求める.
ベクトルに対する微分の以下の式は覚えておいたほうが良い.
線形モデルの最小二乗法について, パラメータの解が
で得られることが分かった.
これをPythonによって実装してみる. 今回は一次関数のパラメータa,b を求め直線フィッティングを行う.
まずデータを生成する. データ生成のアルゴリズムは以下の通りに行った.
- a, b, σを決定する
- [-10, 10]の範囲でデータxをランダムに生成する
- 決定したa, bからt=ax+bを計算する
- t=ax+b+εを計算する. ただしε~N(0, σ^2)
### 単純な線形モデルy = ax + b についてのパラメータa,b を求める問題におけるデータを生成する ### ### a, b, σの値を決定する ### [-x_min, x_max]のデータxをランダムにN個用意 ### 各データに対して, N(ax+b, σ^2)に従うガウス分布からtを生成 ### import numpy as np from numpy.random import seed import matplotlib.pyplot as plt seed(0) a, b, sigma = 2, 3, 3 N = 100 x_min, x_max = -10, 10 # データ生成 X = (x_max-x_min) * np.random.rand(N) + x_min Epsiron = np.random.normal(0, sigma**2, N)# N(0, sigma^2)のN個の正規乱数 T = (a*X + b) + Epsiron # データをファイルに出力 f = open("data00.csv", "w") for (x, t) in zip(X, T): f.write("{}, {}\n".format(x,t)) f.close() # データをプロット plt.scatter(X, T) plt.show()
以下の図のようになった. このデータから,
に従ってパラメータを決定する.
import numpy as np import matplotlib.pyplot as plt # データの読み込み name_data = "data00" path_data = name_data + ".csv" data = np.loadtxt(path_data,delimiter=",") x, t = data[:,0], data[:,1] p = x.shape[0] ones = np.ones(p) x_ = np.c_[ones, x] # パラメータベクトルの計算 A = np.dot(x_.T, x_) w = np.dot(np.dot(np.linalg.inv(A), x_.T), t) print(w) # データのプロット plt.scatter(x, t) # 回帰曲線の描画 b, a = w xx = np.linspace(np.min(x)-2, np.max(x)+2, 100) yy = a * xx + b plt.plot(xx, yy, color="red") plt.xlabel("x") plt.ylabel("y") plt.title("a=2, b=3, σ=2") plt.savefig(name_data.format(a,b)) plt.show()
上図のように実際に目で観ても綺麗にフィットできていることがわかる.
次回はこの線形モデルを更に発展させ, 基底関数, 正則化あたりをやっていきたい.
convert コマンド よく使う操作
画像のリサイズ
a.pngを指定した高さ/幅に同一比のまま縮尺拡大したb.pngを出力する.
# 幅に合わせる convert -resize 640x a.png b.png # 高さに合わせる convert -resize x640 a.png b.png
a.pngという画像を640px * 640pxのb.pngに変換する.
convert -resize 640x640! a.png b.png
ディレクトリ内にある全ての画像に適応するならば
for filename in *.JPG; do convert -resize 640x640! ${filename} ${filename%.JPG}_640x640.jpg; done; # ファイル名を変えずに実行したい場合 mogrify -resize 640x640! *.png;
ディレクトリresize_dirに変換後の画像を格納する場合
mkdir resize_dir for filename in *.JPG; do convert -resize 640x640 $filename reseize_dir/${filename%.JPG}_640x640.JPG; done;
【環境設定】ubuntu 18.04 LTSにslackを入れる
slackのデスクトップアプリケーションを, dpkgでインストールする方法
単純にslackのwebサイトからダウンロードして
$ sudo dpkg -i xxx.deb
とやったらlibappindicator1が無いみたいなエラーがなされたので,
$ sudo apt --fix-broken install $ sudo apt install libindicator1 $ sudo dpkg -i xxx.deb
と行う.
【環境設定】ubuntu18.04のproxy関連設定
aptの設定
自宅でインストールしたubuntuをプロキシ環境下にあるネットワーク環境に持っていったとき, システム全体のプロキシ設定でブラウジングなどできたので, apt installなどできると思っていたが別に設定しないといけないらしい.
参考にしたのはこれ
usado.jp
$ sudo emacs /etc/environment http_proxy="http://proxy-server:port/" https_proxy="http://proxy-server:port/"
gitの設定
$git config --global http.proxy http://proxy.example.com:8080 $git config --global https.proxy http://proxy.example.com:8080
設定場所は.gitconfig
BitBucketの設定
22ポートが閉じられているので443ポートで通信を行う.
$ touch ~/.ssh/config $ emacs ~/.ssh/config
Host bitbucket.org User git IdentityFile ~/.ssh/id_rsa HostName altssh.bitbucket.org Port 443 ProxyCommand connect.exe -H hoge.proxy.jp:1080 %h %p
【環境設定】Ubuntu 18.04LTSでCtrl + Spaceで変換する
mozcを起動してデフォルトでHankaku/Zenkakuの項目をメモして
全く同じものをCtrl+Spaceに置き換えてエントリーを追加すればよい.
mozcを閉じるときに「設定は新しいアプリケーションから適応されます」的なことを言われたので, 新しいアプリを起動して試してみたができなかった.
しかし, 再起動したらできた.
【urllib】プロキシ設定
proxyの使い方メモ
keras.datasets.mnist.load_data()とかしたいときに, プロキシを通す方法
import urllib proxy_support = urllib.request.ProxyHandler({'https': 'http://proxy.hogehoge.ac.jp:80'}) opener = urllib.request.build_opener(proxy_support) urllib.request.install_opener(opener)
【Python】matplotlibで最低限のグラフをかけるようにする
TeXで表示させるため, グラフが必要な時期になってきた.
Pythonで最低限のグラフを描画する方法をメモ.
sigmoid関数のプロット
import numpy as np import matplotlib.pyplot as plt # 関数定義 x = np.linspace(-8, 8, 100) y = 1/ (1+np.exp(-x)) # 垂直線 plt.vlines(0.0, -0.1, 1.1, colors="r", linestyle="dotted", label="") # 水平線 plt.hlines(0.0, -8.0, 8.0, colors="k", linestyle="dotted", label="") plt.hlines(0.5, -8.0, 8.0, colors="k", linestyle="dotted", label="") plt.hlines(1.0, -8.0, 8.0, colors="k", linestyle="dotted", label="") # sigmoid 関数 plt.plot(x, y, label="sigmoid function") # y軸の設定 yticks = [0,0.5,1.0] plt.yticks(yticks) # 軸の名称 plt.xlabel(r"z") plt.ylabel(r"$\phi(z)$") # 凡例 plt.legend(loc="upper left") # 図の保存 plt.savefig("./sigmoid.png") plt.show()
TeXファイルに貼り付ける場合は, .epsにしておく必要がある場合がある.
convert コマンドでファイルの種類を変更する.
$ convert sigmoid.png sigmoid.eps