【Aidamy】 ディープラーニングで手書き文字を識別してみよう③
前回に引き続き、Aidamyの手書き文字認識コースをやっていく。
今回はコース4:データクレンジング。
データクレンジング
機械学習モデルにデータを読みこませるために、データの欠損値や画像の前処理などを行う手法のこと。
lambdaやmapなどの便利なPython記法
無名関数lambda
lambda '引数': '返り値'
で関数を作成することができる。- 引数を取らず'Hello'と出力するだけの関数などは表現することができない。
# func1とfunc2は同じ func1 = lambda x: x ** 2 + x + 3 def func2(x): return x ** 2 + x + 3 # func3とfunc4は同じ # 引数は複数取れる func3 = lambda x, y, z: x * y + z def func4(x, y, z): return x * y + z # func5とfunc6は同じ # ifによる条件分岐を一行でかける func5 = lambda x: x ** 2 + 3 if 0 <= x < 10 else x def func6(x): if 0 <= x < 10: return x ** 2 + 3 else: return x # 以下のように関数名をつけなくても実行できる (lambda x, y: x ** 2 + 3 * y)(3, 8)
listの分割
test_sentence = "this,is a.test sentence" # 通常のsplit test_sentence.split(' ') #=> ["this,is" "a.test" "sentence"] # 複数の記号で分割したい時 import re re.split('[, .]', test_sentence) #=> ["this" "is" "a" "test" "sentence"]
高階関数map
lst = [1, -2, -3, 4, -5] list(map(abs, lst)) #=> [1 2 3 4 5]
filter関数
filter(条件となる関数, 配列)
でlistの各要素から条件を満たす要素だけを取り出す- 条件となる関数は入力に対して
True/False
を返す関数
lst = [1, -2, -3, 4, -5] list(map(lambda x: x > 0, lst)) #=> [1 4]
sorted関数
- sort関数よりも自由度の高いソートができる
sorted(配列, key=キーとなる関数, reverse=False)
nest_list = [ [0, 9], [1, 8], [2, 7], [3, 6], [4, 5] ] # 第二要素をキーとしてソート sorted(nest_list, key=lambda x: x[1]) #=> [[4, 5], [3, 6], [2, 7], [1, 8], [0, 9]]
内包表記
- イテレータを作成するときは
map
やfilter
、リストを作成するときは内包表記
[abs(x) for x in lst] # list(map(abs, lst)) [x for x in lst if x > 0] # list(filter(lambda x: x > 0, lst))
- 多重ループも内包表記で表現することができる
xy_list = [[x, y] for x in x_list for y in y_list] # 以下と同じ xy_list = [] for x in x_list: for y in y_list: xy_list.append([x, y])
defaultdict
- 通常のdict型と同じ様に使える
- 辞書のデフォルト値を決めておくことができる
- デフォルトをintに
from collections import defaultdict lst = ['a', 'b', 'b', 'a', 'b', 'c', 'd'] d = defaultdict(int) for key in lst: d[key] += 1 print(d) #=> defaultdict(<class 'int'>, {'a': 2, 'b': 3, 'c': 1, 'd': 1}) # キーでソート sorted(d.items()) #=> [('a', 2), ('b', 3), ('c', 1), ('d', 1)] # 値で降順ソート sorted(d.items(), key=lambda x:x[1], reverse=True) #=> [('b', 3), ('a', 2), ('c', 1), ('d', 1)]
- デフォルトをlistに
from collections import defaultdict # まとめたいデータprice...(名前, 値段) price = [ ("apple", 50), ("banana", 120), ("grape", 500), ("apple", 70), ("lemon", 150), ("grape", 1000) ] d = defaultdict(list) for k, v in price: d[k].append(v) print(d) print([sum(v) / len(v) for v in d.values()]) # 出力 #=> defaultdict(<class 'list'>, {'apple': [50, 70], 'banana': [120], 'grape': [500, 1000], 'lemon': [150]}) #=> [60.0, 120.0, 750.0, 150.0]
Counter
- 要素の数え上げに特化したクラス
from collections import Counter lst = ['a', 'b', 'b', 'a', 'b', 'c', 'd'] d = Counter(lst) print(d) #=> Counter({'b': 3, 'a': 2, 'c': 1, 'd': 1}) # ソートして上位を出力 print(d.most_common(2) #=> [('b', 3), ('a', 2)]
DataFrameを用いたデータクレンジング
Pandas
import pandas as pd # アヤメデータを取得 iris_df = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data', header=None) # カラム名を指定 iris_df.columns = ["sepal length", "sepal width", "petal length", "petal width", "class"] # 2つのデータを'ID'列でソートし、行番号を振りなおして連結 df1.append(df2).sort_values(by='ID').reset_index(drop=True) # リストワイズ削除 # データ欠損のある行をまるごと消去 df.dropna() # ペアワイズ削除 # データ欠損値の少ない列をだけを残す df[[0, 2]].dropna() # データの欠損値を0埋め df.fillna(0) # データの欠損値を前の値で埋める df.fillna(method='ffill') # データの欠損値をデータの平均値で埋める(平均値代入法) df.fillna(df.mean()) # 重複のある行にTrueと表示 df.duplicated() # 重複データ削除後のデータを表示 df.drop_duplicates() # マッピング # 共通のキーとなるデータに対してテーブルからそのキーに対応するデータを持ってくる area_map ={"Tokyo":"Kanto" ,"Hokkaido":"Hokkaido" ,"Osaka":"Kansai" ,"Kyoto":"Kansai"} city_df['region'] = city_df['city'].map(area_map) #=> 新たに'region'列ができてそれぞれの地域が追加される # bin分割 df_cut = pd.cut(df, bin_num) # 各ビンの数を集計 pd.value_counts(df_cut)
CSV
import csv # csvファイルの作成 with open('students.csv', 'w') as f: writer = csv.writer(f, lineterminator='\f') writer.writerow(['name', 'age', 'school']) writer.writerow(['Taro', 18, 'Handa']) writer.writerow(['Hanako', 16, 'Taketoyo']) writer.writerow(['Toranosuke', 28, 'Azabu']) writer.writerow(['Bakabon', 102, 'Kaisei']) writer.writerow(['Korosuke', 2, 'Asahigaoka'])
OpneCVの利用と画像データの前処理
RGBデータ
- 画像はピクセルと呼ばれる小さな粒の集まりで表現される。
- それぞれのピクセルの色を変えて画像を表現。
- カラー画像はRed, Green, Blue(RGB)で表現。
- 三色の明るさは多くの場合0~255(8bit)の数値
- (255, 0, 0)は赤、(0, 0, 0)は黒、(255, 255, 255)は白
- OpenCVでは一つのピクセルを表すための要素の数をチャンネル数と呼ぶ
- RGB画像はチャンネル数3
- モノクロ画像はチャンネル数1
画像データのフォーマット
- PNG、JPEG、PDF、GIFなどがある
- 可逆圧縮、非可逆圧縮の違い、容量の違いなど一長一短
- JPEG、GIF、PNG、TIFF、BMP…いろいろな画像ファイルと特徴とか参考に
OpenCV
- 画像を扱うのに便利なライブラリ
- インポート
import numpy as np import cv2
- 画像の作成と保存
# 画像の作成、保存 # 画像サイズ img_size = (512, 512) # 緑色の画像 my_img = np.array([[[0, 255, 0] for _ in range(img_size[1])] for _ in range(img_size[0])]) # 画像の保存 cv2.imwrite('my_green_img.jpg', my_img)
my_green_img.jpg
- 画像の読み込みと表示
# 画像の読み込み img = cv2.imread('./sample.jpg') # 画像の表示 # sampleはウィンドウの名前 cv2.imshow('sample', img)
sample.jpg
- トリミングとリサイズ
size = img.shape # 行列の一部を取り出してくればトリミングできる trimed_img = img[:size[0] // 2, :size[1] // 3] # リサイズ resized_img = cv2.resize(trimed_img, (trimed_img.shpe[1] * 2, trimed_img.shape[0] * 2))
trimed_img.jpg
resized_img.jpg
- 回転・反転
img = cv2.imread("./sample.jpg") # warpAffine()を用いるのに必要な行列を作成 # 第一引数:回転の中心(今回は画像の中心) # 第二引数:回転角度(今回は180度) # 第三引数:倍率(今回は2倍に拡大) mat = cv2.getRotationMatrix2D(tuple(np.array(img.shape[:2]) / 2), 180, 2.0) # アフィン変換 # 第一引数:変換したい画像 # 第二引数:上で生成した行列(mat) # 第三引数:サイズ affine_img = cv2.warpAffine(img, mat, img.shape[:2]) # 反転 # 第二引数:反転の軸(0: x軸 / 正: y軸 / 負: xy両方) fliped_img = cv2.flip(img, 0)
affine_img.jpg
fliped_img.jpg
- 色調変換・色反転
- RGB -> Lab色空間
- Lab色空間は人間の視覚に近似するよう設計されている
# Lab色空間に変換 lab_img = cv2.cvtColor(img, cv2.COLOR_RGB2LAB) # モノクロ画像に変換 gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # 画像の色を反転 inverted_img = cv2.bitwise_not(img)
lab_img.jpg
gray_img.jpg
inverted_img.jpg
OpenCVの利用
閾値処理(二値化)
- 画像の容量を小さくするために、一定以上明るいもの、あるいは一定以上暗いものをすべて同じ値にする処理
cv2.threshold()
で実現可能- 第一引数:処理する画像
- 第二引数:しきい値
- 第三引数:最大値
- 第四引数:二値化の仕方
threshold, threshold_img = cv2.threshold(img, 75, 255, cv2.THRESH_BINARY)
threshold_img.jpg
マスキング
- 画像の一部分のみをとりだす
- 白黒でチャンネル数1のマスク用画像を容易
- ある画像のマスク用画像の白い部分と同じ部分だけ抽出
mask = cv2.imread('mask.png', 0) # 第二引数を0にするとチャンネル数1に変換して読み込み mask = cv2.resize(mask, (img.shape[1], img.shape[0])) masked_img = cv2.bitwise_and(img, img, mask = mask)
mask.png
masked_img.jpg
ぼかし
blur_img = cv2.GaussianBlur(img, (21, 21), 0)
blur_img.jpg
ノイズの除去
cv2.fastNlMeansDenoisingColord()
を使う
膨張・収縮
- 主に2値画像で行われる
- あるピクセルを中心とし、フィルタ内の最大値をその中心の値にすることを膨張、最小値をその中心の値にすることを収縮という
- フィルタは、中心のピクセルの上下4つを用いる方法と、自信を囲む8つを用いる方法の2通りが主。
- uint8は8ビットで表された符号なしの整数
filt = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]], np.uint8) # 膨張 dilated_img = cv2.dilate(img, filt) # 収縮 eroded_img = cv2.erode(img, filt)
dilated_img.jpg
eroded_img.jpg
長かった、、、。