深層学習とその他

機械学習したいマン

意識中くらいのお菓子作り

アドベントカレンダー14日目です。
前回は意識低い系お菓子作りをしましたが、今回は意識中くらいはあるお菓子作りをしていきたいと思います。

今回作るもの

スノーボールクッキーを作ります。あの白くて丸いやつです。
作ろうと思った理由として、以前にたくさん買ったアーモンドプードルを消費したいというのと、少しクリスマスぽくていいかなと思ったからです。
今回のレシピはこちらです。
cookpad.com

必要な材料

  • 薄力粉 100g
  • アーモンドプードル(薄力粉で代用可) 40g
  • 粉砂糖(上白糖で代用可)40g
  • バター(サラダ油で代用可)70g

仕上げ用

  • 粉砂糖 大さじ2〜4くらい

前回の材料から見て少し変わっているものは、アーモンドプードルと粉砂糖くらいですね。両方少量であれば100均で売っています。良い世の中ですね。ただし、粉砂糖は溶けるものと溶けないものを使ったほうがきれいかもしれません。アーモンドプードルを代用すると、香ばしさが落ちますが一応作れます。

製作工程

最初に、アーモンドプードルを少し煎りました。この工程は小麦粉で代替する際にはいらないと思います。

バターをクリーム状になるまで混ぜます。この時期は寒いせいでバターがまったく溶けないので、金属製のボウルであれば体温で徐々に溶かすか、湯煎をしていい感じにしましょう。レンジで少し温めてもいいと思います。

次に、粉類三種類を振るいます。振るわないとダマが出来たりして微妙な気がします。自分は粉ふるいを使いましたが、ザルでもいいという意見を頂いたのでそれでもいけると思います。

あとは袋に入れて混ぜるか、ボウルで混ぜてひとかたまりにします。ひとかたまりになったら、適当に小さめに分けながら手のひらで丸めていきます。

オーブンは170度に予熱し、クッキングシートを敷いた天板に並べます。
f:id:looseleaf0727:20171214140406j:plain

あとは15分から25分くらい焼きます。焼き具合を見ながら適度なところで止めてください。個人的には、焼き色が少しついて茶色になり始めたのが出たところで止めました。

焼けたものです。重みで少し潰れたのはミスですね。もう少し一個あたりを小さく作りましょう。
f:id:looseleaf0727:20171214141015j:plain

後は粉砂糖を適当につけました。頭が悪いので焼いた直後につけたら水分が残っているせいで溶けてきれいになりませんでした。その後、改めて泣かない粉糖(水で溶けにくい)をつけたらそれっぽくなりました。
f:id:looseleaf0727:20171214141140j:plain

食べてみての感想

香ばしくてさくさくなのがいいですね。結構ひょいひょい食べられるし、また作りたいかなと思いました。

食べてもらっての感想

  • 無印のものより甘くなくておいしい
  • 香ばしい
  • 一口サイズだったらもっと嬉しかった

まとめ

粉ふるいめちゃ小さいの使ったせいで粉ふるいだけで一時間くらいかかりました。大きいものかザルでも使いましょう。
お菓子自体はクリスマスらしくて、材料はお手軽、味もおいしいのでかなりおすすめです。

chainerのSerial_Iteratorについて

アドベントカレンダー13日目です。大幅に超過してますが許してください。
adventar.org
今日はchainerのSerial Iteratorについて書いていきます。

Serial_Iteratorとは

まずはIteratorの説明を見ていきます。

Base class of all dataset iterators.

Iterator iterates over the dataset, yielding a minibatch at each iteration. Minibatch is a list of examples. Each implementation should implement an iterator protocol (e.g., the __next__() method).

Note that, even if the iterator supports setting the batch size, it does not guarantee that each batch always contains the same number of examples. For example, if you let the iterator to stop at the end of the sweep, the last batch may contain a fewer number of examples.

The interface between the iterator and the underlying dataset is not fixed, and up to the implementation.

chainer.dataset.Iterator — Chainer 3.2.0 documentation

TupleDatasetなど、Datasetを引数にとり、バッチサイズ分だけ返してくれる関数です。
学習の際に使います。

内部での処理

まずは__init__を見ていきます。値を受け取るくらいですね。

def __init__(self, dataset, batch_size, repeat=True, shuffle=True):
        self.dataset = dataset
        self.batch_size = batch_size
        self._repeat = repeat
        self._shuffle = shuffle

        self.reset()

次に__next__を見ていきます。これが重要な部分になります。

def __next__(self):
        if not self._repeat and self.epoch > 0:
            raise StopIteration

        self._previous_epoch_detail = self.epoch_detail

        i = self.current_position
        i_end = i + self.batch_size
        N = len(self.dataset)

        if self._order is None:
            batch = self.dataset[i:i_end]
        else:
            batch = [self.dataset[index] for index in self._order[i:i_end]]

        if i_end >= N:
            if self._repeat:
                rest = i_end - N
                if self._order is not None:
                    numpy.random.shuffle(self._order)
                if rest > 0:
                    if self._order is None:
                        batch.extend(self.dataset[:rest])
                    else:
                        batch.extend([self.dataset[index]
                                      for index in self._order[:rest]])
                self.current_position = rest
            else:
                self.current_position = 0

            self.epoch += 1
            self.is_new_epoch = True
        else:
            self.is_new_epoch = False
            self.current_position = i_end

        return batch

初期設定などはここで行われます。
shuffleを行う場合には、データセットの長さがからpermutationを行います。

def reset(self):
        if self._shuffle:
            self._order = numpy.random.permutation(len(self.dataset))
        else:
            self._order = None

        self.current_position = 0
        self.epoch = 0
        self.is_new_epoch = False

        # use -1 instead of None internally.
        self._previous_epoch_detail = -1.


batchを返す準備は以下で行われます。スライシングする範囲の最初と最後を計算します。データセットのサイズを同時に計算します。shuffleを行わない場合には、データセットからスライシングしてそのままbatchに代入します。shuffleを行う場合には、permutationを使い、バッチサイズ分だけbatchに代入していきます。

i = self.current_position
i_end = i + self.batch_size
N = len(self.dataset)

if self._order is None:
    batch = self.dataset[i:i_end]
else:
    batch = [self.dataset[index] for index in self._order[i:i_end]]

スライシングがバッチサイズを上回ったときのための処理です。
上回った場合では、データセットのサイズまでにスライシングします。
エポックのインクリメントなども行われます。

if i_end >= N:
            if self._repeat:
                rest = i_end - N
                if self._order is not None:
                    numpy.random.shuffle(self._order)
                if rest > 0:
                    if self._order is None:
                        batch.extend(self.dataset[:rest])
                    else:
                        batch.extend([self.dataset[index]
                                      for index in self._order[:rest]])
                self.current_position = rest
            else:
                self.current_position = 0

            self.epoch += 1
            self.is_new_epoch = True
        else:
            self.is_new_epoch = False
            self.current_position = i_end

        return batch

まとめ

ミニバッチを取り出す処理を行うわけですが、シャッフルできる機能があったり、リピート機能があったりして思っていたよりはいろんな機能があったことをしれてよかったです。次はこれを使うupdaterあたりを見ていこうと思います。

ChainerのTupleDatasetについて

アドベントカレンダー12日目です。
adventar.org
今日はChainerのTupleDatasetについて書いていきます。

TupleDatasetとは

複数のデータセットからTupleのデータセットを作成します。
https://docs.chainer.org/en/stable/reference/generated/chainer.datasets.TupleDataset.html#chainer.datasets.TupleDataset

Chainerの公式Exampleの中で有名なMNISTでも用いられています。
MNISTでは、データセットの準備の際にget_mnist()と呼んでいます。

# Load the MNIST dataset
train, test = chainer.datasets.get_mnist()

get_mnist()の中を見に行くと、このように書かれています。

if withlabel:
        labels = raw['y'].astype(label_dtype)
        return tuple_dataset.TupleDataset(images, labels)

ラベルがある場合には、画像とラベルを引数にTupleDatasetを呼んでいます。

内部での処理

まずdatasetsが渡されているか確認します。datasets[0]の長さをとり、enumerateを使い、datasetsの長さが全て一致しているか確認します。問題が無ければ、self.datasetsとlengthに値をセットします。

def __init__(self, *datasets):
        if not datasets:
            raise ValueError('no datasets are given')
        length = len(datasets[0])
        for i, dataset in enumerate(datasets):
            if len(dataset) != length:
                raise ValueError(
                    'dataset of the index {} has a wrong length'.format(i))
        self._datasets = datasets
        self._length = length

getitemでは、datasetsの分for分を回し、dataset[index]をbatchesに代入し、
isinstanceがtrueの場合は、batches[0]の長さを確認しfor文を回して、tupleで値を返す。
falseの場合は、そのままbatchesをtupleにして値を返す。

def __getitem__(self, index):
        batches = [dataset[index] for dataset in self._datasets]
        if isinstance(index, slice):
            length = len(batches[0])
            return [tuple([batch[i] for batch in batches])
                    for i in six.moves.range(length)]
        else:
                return tuple(batches)

まとめ

TupleDataset自体はそこまで複雑な処理は行っておらず、基本的な長さの確認をしたりして、データを扱いやすくするための関数でした。これを理解することで、SerialIteratorなども読みやすくなると思います。

scikit-learnのgrid_search使ってみた

アドベントカレンダー11日目です。
adventar.org
今日はgrid_searchについて書いていきたいと思います。

grid_searchとは?

最適なパラメータの探索を行うメソッドです。
探索を行いたいパラメータをセットしておくと、最適なパラメータを見つけ出してくれます。何も考えずにSVMとか動かすと精度はあまり良い数値になりません。ただ、手動で探すのはとても大変なので、そこでgrid_searchを使います。

使い方

まず分類器を使う準備をしましょう。
必要なものをインポートします。

from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
from sklearn.cross_validation import train_test_split
from sklearn.datasets import load_digits
from sklearn.metrics import classification_report
import numpy as np
import time


今回使うデータは、以前も使った数字のデータです。ついでに訓練データとテストデータに分けます。

digits = load_digits()
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target)


SVMでgrid_searchを試して見る前に、何もパラメータをセットしないときの精度を確認しましょう。

clf = SVC()
clf.fit(X_train, y_train)

>>> 
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)


学習は一瞬で終わります。精度を見てみましょう。
芳しくないですね。

np.mean(y_test == clf.predict(X_test))
>>> 0.45777777777777778


次にgrid_searchを試してみましょう。パラメータは以下のように記述することができます。順に試しながら最適なものを探索してくれます。GridSearchでは、Accでスコアを、交差検定を5回に設定しました。

params = {
    "C":np.arange(0.1,1,0.05),
    "kernel":["linear", "poly", "rbf", "sigmoid"],
    "gamma":np.arange(0.0001,0.1,0.05)
}
grid = GridSearchCV(clf, params,scoring="accuracy", cv=5)


あとはこれを実行するだけです。
まあまあ時間がかかることが予測されるので、ついでに時間を測ります。

start = time.time()
grid.fit(X_train, y_train)
print("time",time.time() - start)
>>> time 113.73304891586304


best_params_で指定したパラメータの中で、最適だったパラメータを見れます。ちょっと楽しいですね。

grid.best_params_
>>>  {'C': 0.10000000000000001, 'gamma': 0.050100000000000006, 'kernel': 'poly'}


best_score_で最もよかったパラメータの際の精度が見れます。こんな精度いいの?本当なのかなあ。

grid.best_score_
>>> 0.98069784706755758


best_estimatorで最もよかった際の学習のパラメータが表示できます。

grid.best_estimator_
>>>
SVC(C=0.10000000000000001, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma=0.050100000000000006,
  kernel='poly', max_iter=-1, probability=False, random_state=None,
  shrinking=True, tol=0.001, verbose=False)


せっかくなのでclassification reportも使いましょう。

pred = grid.predict(X_test)
print(classification_report(y_test, pred))

>>>
             precision    recall  f1-score   support

          0       1.00      1.00      1.00        39
          1       0.98      1.00      0.99        46
          2       1.00      1.00      1.00        43
          3       0.98      0.98      0.98        41
          4       1.00      1.00      1.00        55
          5       1.00      1.00      1.00        43
          6       1.00      1.00      1.00        46
          7       1.00      1.00      1.00        48
          8       0.97      0.97      0.97        39
          9       1.00      0.98      0.99        50

avg / total       0.99      0.99      0.99       450

これを見る限り、ちゃんと良いパラメータだったみたいですね。テストデータでもいい精度が出ています。

まとめ

grid_searchすごい、とても便利

意識低い系お菓子作り

アドベントカレンダー10日目です。

今日は意識低めのお菓子作りについて書いていきたいと思います。

これを書こうと思った理由

お菓子作りは少しハードルが高く見えるせいで、お菓子作りするひとが少ないのかな?
そうだったら、意識低めのお菓子作りの記事を書くことでお菓子作りをする人が増えればなあということです。

今回作るもの

パウンドケーキを作ります。
意識を低くするため、ダイソーで売ってたパウンドケーキミックスを使います。
f:id:looseleaf0727:20171210184227j:plain

必要な材料

f:id:looseleaf0727:20171210184327j:plain
パウンドケーキミックスの袋いわく、必要材料は以下のものです。

  • パウンドケーキの型
  • この粉
  • 卵 一個
  • 無塩バター 60g

普通にイメージするのは金属の型ですが、高いうえに普通はそこまで使いません。100円ショップはお菓子を作る人には素晴らしい店なので、紙のパウンドケーキの型が売ってます。これを使いましょう。
f:id:looseleaf0727:20171210184255j:plain

無塩バターがあると風味がよくなりますが、まあまあの値段です。200gで400円とか。無塩のマーガリンを使うと、バターの風味自体はなくなりますが、半額くらいで買えます。適当な油とかを使うと風味はそこまでよくないですが、最初から液体だし安いので楽です。今回はオリーブ油を使います。

必要な道具

今回必要なのはこれくらいだと思います。

  • はかり
  • ボウル
  • ゴムベラ
  • 泡立て器

今回は、油の計量に使います。はかりは1gから計れるものがおすすめです。電子はかりがとても便利ですが、人によってはもっていないと思います。そういう場合には計量スプーンを代わりに使うことが出来ます。計量スプーンでの換算サイトがあるので、これを使いましょう。計量スプーンすらない場合には、100均で買いましょう。
www.benricho.org

ボウルは好みですが、金属で深いものが好きです。なんも考えずバター入れたりして、後から溶かしていれなくちゃいけないのに気づいたときに湯煎でなんとかできたりします。

ゴムベラは無いと死にます。100均で買いましょう。先端が取り外せないものがおすすめです。

泡立て器もないと死にます。100均で買いましょう。

材料と道具を並べるとこんな感じです。実家だと大抵ありそうですね。
f:id:looseleaf0727:20171210184403j:plain

製作工程

まず卵をほぐします。めんどいので泡立て器で。
f:id:looseleaf0727:20171210184349j:plain

次に、バターを入れます。高いので油にしました。
f:id:looseleaf0727:20171210184402j:plain

そしてダイソーで買ったパウンドケーキミックスを入れます。
f:id:looseleaf0727:20171210184412j:plain

まぜたらカスタードクリームみたいになりました。
f:id:looseleaf0727:20171210184445j:plain

型にバターを塗ります。高いので油です。スプーンで適当に塗ります。もし塗らないと焼いたときに張り付いたりします。
f:id:looseleaf0727:20171210184427j:plain

ゴムベラで入れます。こういうときに極めて便利。
f:id:looseleaf0727:20171210184503j:plain

なんか汚いですがこんなもんです。表面ならしてもいいですが、めんどいのでしません。
f:id:looseleaf0727:20171210184454j:plain

天板に乗せて焼きます。予熱は流石にしましょう。オーブンがないと戦えません。
f:id:looseleaf0727:20171210184453j:plain

できました。なんかしらないけど結構膨らみましたね。
f:id:looseleaf0727:20171210184504j:plain

紙の型を使う利点として、洗い物が減る、型からの取り外しが簡単、このまま包丁で切れるなどがあります。
f:id:looseleaf0727:20171210184513j:plain

食べてみての感想

少しオリーブ油の匂いがしますが、まあまあ美味しいです。マーガリンを使ったら間違いなく普通に美味しいですね。

まとめ

すごい適当でもお菓子は作れました。
今回安い油を使いましたが、こだわればカルピスバターだったり、発酵バターだったりすると思います。パソコンを組む時と同じで、ここにはお金かけたい、ここはある程度削りたいとかすると結構楽しいと思います。

chainerのto_cpuについて

アドベントカレンダー9日目です。
adventar.org
今日はchainerのmodel.to_cpuについて書いていきます。

to_cpuについて

chainerで学習する際には、ネットワークを定義した後、モデルをto_gpuすると思います。これによりGPUで処理をすることができるからです。対して、to_cpuはどのような場面で使うかというと、学習後のモデルを保存する際に使用したりします。

model.to_cpu()
chainer.serializers.save_npz("model.npz", model)


to_cpuのメリットは?

to_cpuを使うことによるメリットとして、cpuで処理をすることができることです。学習後のモデルを使って、webサービスか何かに使うときには、貧弱なサーバーではGPUを使えないことがあります。そういうときには、to_cpuしたモデルを使ってCPUのみで処理するのがいいかなと思います。

to_cpuの注意点

学習ループ中に、model.to_cpuしてそのままだとエラーを吐きます。そういうときの正しい対処法はわかりませんが、とりあえずto_gpuすることでエラーを吐かずに走り続けます。

まとめ

弱いパソコンで試すときや、予測だけするときにto_cpuを使うと便利かも知れません。

chainer以外でのcupyの使用について

アドベントカレンダー8日目です。
今日はcupyについて書いていきたいと思います。

速度比較

今回は、0~300000000までの配列を生成し、それをシグモイド関数に通します。その時の速度を以下の3つで比較します。

def sigmoid(x):
    temp = x.copy()
    for i,x in enumerate(x):
        temp[i] = 1/(1+math.exp(-x))
    return temp

start = time.time()
x = list(range(300000000))
sigmoid(x)
print(time.time()-start)
  • numpyのarangeとnp.exp
def numpy_sigmoid(x):
    return 1/(1+np.exp(-x))

start = time.time()
x = np.arange(300000000)
numpy_sigmoid(x)
print(time.time()-start)
  • cupyのarangeとcupy.exp
def cupy_sigmoid(x):
    return 1/(1+xp.exp(-x))

start = time.time()
x = xp.arange(300000000)
numpy_sigmoid(x)
print(time.time()-start)

実行結果

何秒かかったか以下に示します。

80.69074440002441

  • numpy

5.5577309131622314

  • cupy

0.0005419254302978516

numpyを使うことでかなり早くなってますね。
cupyは異次元です*2

まとめ

やはりGPUを使うと異常に早いですね。
chainer以外にもみなさんつかってみてください。

検証のソースコードは以下になります。

*1:sigmoidの返り値があまりきれいではない。リスト内包表記とかでもう少し早くなりそう

*2:ただしGPU Memoryを7709MB使用した