意識中くらいのお菓子作り
アドベントカレンダー14日目です。
前回は意識低い系お菓子作りをしましたが、今回は意識中くらいはあるお菓子作りをしていきたいと思います。
今回作るもの
スノーボールクッキーを作ります。あの白くて丸いやつです。
作ろうと思った理由として、以前にたくさん買ったアーモンドプードルを消費したいというのと、少しクリスマスぽくていいかなと思ったからです。
今回のレシピはこちらです。
cookpad.com
必要な材料
- 薄力粉 100g
- アーモンドプードル(薄力粉で代用可) 40g
- 粉砂糖(上白糖で代用可)40g
- バター(サラダ油で代用可)70g
仕上げ用
- 粉砂糖 大さじ2〜4くらい
前回の材料から見て少し変わっているものは、アーモンドプードルと粉砂糖くらいですね。両方少量であれば100均で売っています。良い世の中ですね。ただし、粉砂糖は溶けるものと溶けないものを使ったほうがきれいかもしれません。アーモンドプードルを代用すると、香ばしさが落ちますが一応作れます。
製作工程
最初に、アーモンドプードルを少し煎りました。この工程は小麦粉で代替する際にはいらないと思います。
バターをクリーム状になるまで混ぜます。この時期は寒いせいでバターがまったく溶けないので、金属製のボウルであれば体温で徐々に溶かすか、湯煎をしていい感じにしましょう。レンジで少し温めてもいいと思います。
次に、粉類三種類を振るいます。振るわないとダマが出来たりして微妙な気がします。自分は粉ふるいを使いましたが、ザルでもいいという意見を頂いたのでそれでもいけると思います。
あとは袋に入れて混ぜるか、ボウルで混ぜてひとかたまりにします。ひとかたまりになったら、適当に小さめに分けながら手のひらで丸めていきます。
オーブンは170度に予熱し、クッキングシートを敷いた天板に並べます。
あとは15分から25分くらい焼きます。焼き具合を見ながら適度なところで止めてください。個人的には、焼き色が少しついて茶色になり始めたのが出たところで止めました。
焼けたものです。重みで少し潰れたのはミスですね。もう少し一個あたりを小さく作りましょう。
後は粉砂糖を適当につけました。頭が悪いので焼いた直後につけたら水分が残っているせいで溶けてきれいになりませんでした。その後、改めて泣かない粉糖(水で溶けにくい)をつけたらそれっぽくなりました。
食べてみての感想
香ばしくてさくさくなのがいいですね。結構ひょいひょい食べられるし、また作りたいかなと思いました。
食べてもらっての感想
- 無印のものより甘くなくておいしい
- 香ばしい
- 一口サイズだったらもっと嬉しかった
まとめ
粉ふるいめちゃ小さいの使ったせいで粉ふるいだけで一時間くらいかかりました。大きいものかザルでも使いましょう。
お菓子自体はクリスマスらしくて、材料はお手軽、味もおいしいのでかなりおすすめです。
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日目です。
今日は意識低めのお菓子作りについて書いていきたいと思います。
これを書こうと思った理由
お菓子作りは少しハードルが高く見えるせいで、お菓子作りするひとが少ないのかな?
そうだったら、意識低めのお菓子作りの記事を書くことでお菓子作りをする人が増えればなあということです。
必要な材料
パウンドケーキミックスの袋いわく、必要材料は以下のものです。
- パウンドケーキの型
- この粉
- 卵 一個
- 無塩バター 60g
普通にイメージするのは金属の型ですが、高いうえに普通はそこまで使いません。100円ショップはお菓子を作る人には素晴らしい店なので、紙のパウンドケーキの型が売ってます。これを使いましょう。
無塩バターがあると風味がよくなりますが、まあまあの値段です。200gで400円とか。無塩のマーガリンを使うと、バターの風味自体はなくなりますが、半額くらいで買えます。適当な油とかを使うと風味はそこまでよくないですが、最初から液体だし安いので楽です。今回はオリーブ油を使います。
必要な道具
今回必要なのはこれくらいだと思います。
- はかり
- ボウル
- ゴムベラ
- 泡立て器
今回は、油の計量に使います。はかりは1gから計れるものがおすすめです。電子はかりがとても便利ですが、人によってはもっていないと思います。そういう場合には計量スプーンを代わりに使うことが出来ます。計量スプーンでの換算サイトがあるので、これを使いましょう。計量スプーンすらない場合には、100均で買いましょう。
www.benricho.org
ボウルは好みですが、金属で深いものが好きです。なんも考えずバター入れたりして、後から溶かしていれなくちゃいけないのに気づいたときに湯煎でなんとかできたりします。
ゴムベラは無いと死にます。100均で買いましょう。先端が取り外せないものがおすすめです。
泡立て器もないと死にます。100均で買いましょう。
材料と道具を並べるとこんな感じです。実家だと大抵ありそうですね。
製作工程
まず卵をほぐします。めんどいので泡立て器で。
次に、バターを入れます。高いので油にしました。
まぜたらカスタードクリームみたいになりました。
型にバターを塗ります。高いので油です。スプーンで適当に塗ります。もし塗らないと焼いたときに張り付いたりします。
ゴムベラで入れます。こういうときに極めて便利。
なんか汚いですがこんなもんです。表面ならしてもいいですが、めんどいのでしません。
天板に乗せて焼きます。予熱は流石にしましょう。オーブンがないと戦えません。
できました。なんかしらないけど結構膨らみましたね。
紙の型を使う利点として、洗い物が減る、型からの取り外しが簡単、このまま包丁で切れるなどがあります。
食べてみての感想
少しオリーブ油の匂いがしますが、まあまあ美味しいです。マーガリンを使ったら間違いなく普通に美味しいですね。
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つで比較します。
- pythonのrangeとmath.exp
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。