はるみちゃんのてっくぶろぐ

はるみちゃんのブログだよ。主に技術系の記事を書くよ。

DEEPTONEWORKSがVRに対応しました

前回紹介したA-frameを使ってVR対応Webサイトを作ってみました。
と、いっても非常に簡単なデモのようなものですが。

http://deeptoneworks.com/vr/

どういう見せ方をしたら新しさを感じるんだろう。。
そこら辺ちょっと考えないといけないなあ

HTMLで簡単WebVR!A-Frameの紹介

A-Frameとは?

Mozilla VR teamが開発したWebVRフレームワーク
HTMLを書くだけでWebVR開発ができる優れもの

aframe.io

Entity-Component-System

A-FRAMEはEntity-Component-Systemというパターンで実装されている。

エンティティ・コンポーネント・システム - Wikipedia

ざっくり言うと、汎用的なオブジェクト「エンティティ」に「コンポーネント」をどんどんくっつけていく感じ。
外観を設定するコンポーネントや、アニメーションを行うコンポーネント、イベントを行うコンポーネントなどが存在します。
f:id:deeptoneworks:20161027221208g:plain
(転載:https://aframe.io/docs/0.2.0/core/#composition)

Entity

全ての基本になるのがa-entity。
こいつの属性値としてコンポーネントをどんどん追加していくわけですね。

<a-entity>

JavaScriptのDOM APIを経由してこのエンティティのオブジェクトを取得できます。
エンティティには様々なプロパティ、メソッドがあり操作可能です。

Entity – A-Frame

状態の登録方法やプロパティの取得,変更などの重要な機能が提供されていることがわかりますね。

Component

a-entityの属性値として登録するコンポーネントです。
どんなコンポーネントが存在するのかはドキュメントを参照してください。

さて、コンポーネントはAFRAME.registerComponentメソッドにより独自で追加することも可能です。

AFRAME.registerComponent(‘position’, {
    schema: { type: ‘vec3’ },
    update: function () {
        var object3D = this.el.object3D;
        var data = this.data;
        object3D.position.set(data.x, data.y, data.z);
    }
});

schemaでどんな値を取得していくるかを決めるんですね。
定義の仕方は以下のような感じです。

schema: {
 type: ‘int’, default: 5
}

で、あとはupdateの部分でthree.jsのクラスであるobject3Dのオブジェクトを取得して、
こいつのプロパティを変更すればOKです。

Animation

a-entityの子としてa-animation entityを定義することによってアニメーションが実装できます。

<a-entity>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-animation attribute="rotation"
dur="10000"
fill="forwards"
to="0 360 0"
repeat="indefinite">
</a-animation>
</a-entity>

たったこれだけで以下のようなアニメーションになります。凄いですね!
f:id:deeptoneworks:20161027231726g:plain

ちなみに<a-sphere>は<a-entity geometry=“primitive: sphere>のエイリアスです。

結局どんなことができるの?

ドキュメントを読んでわかった、A-FRAMEでできる事を列挙していきます。

・3Dモデルをロード
・イベントを登録(fuseが許可されてる場合,目でクリックイベントを発火させる事も可能)
・霧の表示 ・立方体/球/円錐/円柱/板・・などを作成。トーラスのような複雑な形状も定義されたりします。
・シェーダーを変更
・位置/倍率/回転角度を変更
・アニメーション
・オブジェクトAにオブジェクトBを追従
・Rayを飛ばす
・音を鳴らす
・表示非表示を切り替える
・WASDボタンで移動する
・360度画像/動画を表示する
・通常の画像/動画を表示する

などなど。

あと、サンプル集がとても参考になります。

A-Frame Examples

便利なツールとか

A-FRAME INSPECTOR

f:id:deeptoneworks:20161028012059p:plain

github.com

シーンをビジュアライズしてGUIで作成可能なツールです。
静的なシーンはこれだけで作れちゃいます!凄いですね・・。

MagicaVoxel

f:id:deeptoneworks:20161028012043p:plain

ephtracy.github.io

本来3Dオブジェクトを作るのは大変ですけど、MagicaVoxelでならまるでマインクラフトをやってるかのように作れちゃいます!
もちろん、A-FRAMEで読み込めます。
これなら自分でも3Dが作れそうです!!

flickr

www.flickr.com

おなじみflickr
Equirectangularで検索すると360度パノラマ写真を入手できます。

Search | Flickr(検索結果)

textures.com

www.textures.com

3Dで使えそうなテクスチャがたくさんあります。

感想

楽しそうや・・すごく簡単に色々できる事がわかったし、何か作ってみたいなあ・・

VB CODEで整数列ファイルの圧縮

大規模サービズ技術入門に紹介されている圧縮プログラミングを試してみます。
圧縮対象のデータは

http://gihyo.jp/book/2010/978-4-7741-4307-1/support#supportDownload
からダウンロードできるeid_tags.txtというデータです。
中身は「タイトル 数字1,数字2・・」と一行ずつデータが書かれています。
整数列を圧縮することでこの180MBのデータを何MBになるのか、を確かめてみます。

何故データの圧縮が必要なのか

大きなデータを扱う際,そのデータがメモリに乗るか乗らないかで速度に大きな差がでてしまう。
メモリからデータを読む速度と,ディスクから読む速度は数十万倍くらいディスクから読む方が遅くなる。
なので、大きなデータを圧縮してメモリに乗り切るサイズにすることが必要。

VB CODE(Variable Byte Code)について

VBCodeは整数の符号化方法の1つ。

一般に整数は固定長バイナリ符号で書き込まれる。
32bit intの5なら00000000 00000000 00000000 00000101
00000000 の部分が無駄なので、これを圧縮する。

例えば、5だと10000101と圧縮できる。これは先頭の1bitが「このバイト列で終わる」というフラグの意味になっている。
130だと、00000001 10000010となる。
まず最初のバイト列の先頭は0なので、このバイト列では終わらない事がわかる。
次のバイト列を見てみると先頭が1になっているのでこのバイト列で終わり。
注意すべき点は最初のバイト列の最後のビットが128という数字を表すことになっている点(各バイト列の最初はフラグに使用するため)

実装(1) VBCodeの実装

VBCodeを実装してみる。本にのっている疑似コードをpythonで書く。

import struct
def encode(num):
    bytes = [] while True:
    bytes.insert(0,num%128)
    if num < 128:
        break
        num /= 128
        bytes[-1] += 128
    return struct.pack(‘%dB’ % len(bytes),*bytes)

def decode(bytes):
    numbers = [] n = 0
    bytes = struct.unpack(‘%dB’ % len(bytes),bytes)
    for b in bytes:
        if b < 128:
            n = 128*n + b
        else:
            n = 128*n + (b – 128)
            numbers.append(n)
            n = 0
    return numbers

実装(2) ファイルのエンコード・デコード

先に作ったVBCodeのencode,decodeをしてくれるモジュールを使用してファイルをエンコード/デコードする。
(雑な実装ですいません><)

(エンコード

#coding utf-8
import vb_code as vb
import binascii
import struct
f = open(‘eid_tags.txt’,‘rw’)
outFile = open(‘eid_tags_encode.bin’,‘ab’)
for line in f:
    title,content = line.split("\t")
    params = [x for x in content.split(",")] vb_vals = [] params_len = 0
    for p in params:
        val = vb.encode(int(p))
        params_len += len(val)
        vb_vals.append(val)
        row = "{}{}{}".format(struct.pack(‘2i’,len(title),params_len),title,”.join(vb_vals))
        outFile.write(row)

(デコード)

#coding utf-8
import vb_code as vb
import struct
f = open(‘eid_tags_encode.bin’,‘rb’)
while True:
    bytes = f.read(8)
    if not bytes:
        break
        title_len,params_len = struct.unpack(‘2i’,bytes)
        title = f.read(title_len)
        params = [] print(title)
        for p in vb.decode(f.read(params_len)):
            print(p)

注意すべき点は、バイナリデータを扱っているのでカンマやタブなどの文字を区切り文字とすることができない点。
なので、最初の4バイトに「タイトル文字列の長さ」,次の4バイトに「データ文字列の長さ」を格納している。
デコード時に最初に8バイト分読み取っているのはこの長さを得るため。
この長さの分を「1つのデータ」として区切る。

結果(1)

エンコードの結果、180MBが85MBにまで削減された。
デコードもちゃんと出来ているので問題なさそう。

データの特徴を活かして更に圧縮

今回の圧縮対象のデータには、10,32,45,55・・のようにソートされているという特徴がある。
なので、これまでの値との差分だけを保存してやればデコードができる。
例えば10,32,45,55であれば、「10,22,13,10」となる。
VBCodeは数字が小さいほど圧縮率が高まるので、良い方法と言える。

実装(3) 先のエンコード/デコードするプログラムを改良する

エンコード

#coding utf-8
import vb_code as vb
import binascii
import struct
f = open(‘eid_tags.txt’,‘rw’)
outFile = open(‘eid_tags_encode.bin’,‘ab’)
for line in f:
    title,content = line.split("\t")
    params = [x for x in content.split(",")] vb_vals = [] params_len = 0
    sum_param = 0
    for p in params:
        p = int(p)
        p -= sum_param
        sum_param += p
        val = vb.encode(p)
        params_len += len(val)
        vb_vals.append(val)
        row = "{}{}{}".format(struct.pack(‘2i’,len(title),params_len),title,”.join(vb_vals))
        outFile.write(row)

(デコード)

#coding utf-8
import vb_code as vb
import struct
f = open(‘eid_tags_encode.bin’,‘rb’)
while True:
    bytes = f.read(8)
    if not bytes:
        break
    title_len,params_len = struct.unpack(‘2i’,bytes)
    title = f.read(title_len)
    params = [] print(title)
    sum_param = 0
    for p in vb.decode(f.read(params_len)):
        print(int(p) + sum_param)
        before_param += int(p)

エンコード,デコードともにこれまでの値をsum_paramとして保存し、その差だけを符号化,復号化している。

結果(2)

エンコードの結果、180MBが41.5MBにまで削減された。
因みにzipで圧縮したら55.7MBだったのでそれより良い圧縮率。
やはりデータの特性に合わせた圧縮方法を選択することでより良い結果が得られることがわかった。

所感

バイナリ扱うのって慣れてなくて難しい・・

ディープネットの事前学習・その他の自己符号化器

MLPシリーズ「深層学習」の5.6ディープネットの事前学習と5.7その他の自己符号化器のまとめです。

ディープネットの事前学習

多層の順伝播型ネットワークには勾配消失という現象が起こるため、学習が難しい。
これを解決するのが事前学習。

一般にネットワークの重みはランダムで初期化される。
この重みの初期値をもっと良いものにしておけば、勾配消失が起きないのでは?というアイデア
実際、経験則的に事前学習がうまくいくことが知られている。

勾配消失問題

勾配消失問題は何故起きるのか。

ニューラルネットワークの計算には順伝搬と逆転伝搬がある。
順伝搬は活性化関数を通すので出力は非線形になる。
非線形であれば、何層になっても出力が発散することはない。つまり勾配消失問題は起こらない。
一方、逆伝搬は線形計算である。
線形な計算を何度繰り返しても線形なので逆伝搬の誤差も線形計算である。
この時、各層の重みが大きいと、誤差が伝搬するうちに発散する、逆に重みが小さいと急速に消失し0になってしまう。
このため、重みの更新がうまくできない勾配消失問題が発生する。

積層自己符号化器

重みを事前に学習する方法として、積層自己符号化器というものがある。
以下のような多層の順伝播型ネットワークの重み,バイアスの初期値を積層型自己符号化器によって得る事を考える。
f:id:deeptoneworks:20161021133302p:plain

まず、上記の多層順伝播型ネットワークを層毎に分解し、それぞれを単一の自己符号化器とみなす。
f:id:deeptoneworks:20161021133512p:plain
それぞれを単一の自己符号化器とみなす、とは以下のような図の構成を意味する。
f:id:deeptoneworks:20161021133622p:plain
これらの自己符号化器の重みWnとバイアスbnをそれぞれ学習する。

最後に、上記の自己符号化器により得た重み,バイアスを初期値として多層順伝播型ネットワークの初期値とする。
ただし、最後にランダムに重みを初期化した層を1層追加する。
f:id:deeptoneworks:20161021133957p:plain
以上で、積層型自己符号化器による重み,バイアスの初期値の決定ができた。

その他の自己符号化器

多層自己符号化器

自己符号化器は基本的には一層であるが、これを多層にしたものを多層自己符号化器とよぶ。
f:id:deeptoneworks:20161021134254p:plain
f:id:deeptoneworks:20161021134310p:plain

デノイジング自己符号化器(雑音除去自己符号化器)

自己符号化器の入力,出力に確率的な考え方を追加した自己符号化器。
通常の自己符号化器より良い性能になることが知られている。

構築方法

ネットワークは通常の自己符号化器と全く同じであるが、 入力と出力(教師)のデータにノイズ(雑音)を加えたものを自己符号化器を構築する。
この自己符号化器の出力はノイズののったデータをx’とすると以下である。
f:id:deeptoneworks:20161021135042p:plain

そして、この出力と元のデータ(ノイズが乗っていないデータ)との差を誤差関数として学習する。
誤差関数は活性化関数が恒等写像であれば二乗誤差、
f:id:deeptoneworks:20161021134912p:plain

シグモイド関数であれば交差エントロピーなどを誤差関数として用いる。
f:id:deeptoneworks:20161021134925p:plain

ノイズの例

・ガウシアンノイズ
平均x,分散ρ2ガウス分布に従うノイズ

・マスク状のノイズ
適当な割合でランダムに要素を選択し、その値を0にする

・ソルト&ペッパーノイズ
適当な割合でランダムに要素を選択し、更にそこからランダムで上限値or下限値にする。
例えば、出力の上限,下限が1~0なら、50%の確率で1に,50%の確率で0にする。

データの白色化

MLPシリーズ「深層学習」の5.5節「データの白色化」についてのまとめです。

白色化とは?

成分間(データの特徴間)の相関をなくす処理のこと。
自己符号化器が良い特徴を学習できるかどうかを大きく左右することがある。

考え方

データxの任意の2成分間で相関がなくなる、ということは
つまり、共分散行列が対角行列になれば良い、ということ。

例えば、データxが3次元だとすれば、それらのデータ郡の分散共分散行列は下記のように表せる。

f:id:deeptoneworks:20161017152802p:plain

この中でσ13はデータx1とx3間の相関を表している。
これらの部分が0になれば(=対角行列であれば)成分間の相関がなくなった、といえる。

では、共分散行列を対角行列にするためには何を考えたら良いのかだが、
データxnがある線形変換Pによってun写像されるとし、
この変換後のunの共分散行列が対角行列になるようなPを考えると良いことになる。
f:id:deeptoneworks:20161017153613p:plain

線形変換Pの導出

まずは、「考え方」で示した部分を数式化しておく。

・データxの共分散行列
f:id:deeptoneworks:20161017155608p:plain
※各データの成分はそのデータの平均値を引いた処理を行っている(x - xとして処理済み)

・線形変換Pを施した後のデータun
f:id:deeptoneworks:20161017153613p:plain

・変換後のデータuの共分散行列
f:id:deeptoneworks:20161017155859p:plain

このΦU が対角行列に成ることが目標なのだが、ここで目標とする対角行列をI(=単位行列)とする。

すると、先の式はP=UXを用いて以下のように変形することができる。

f:id:deeptoneworks:20161017160933p:plain

※変形に
f:id:deeptoneworks:20161017161206p:plain,f:id:deeptoneworks:20161017161217p:plain
を用いている

そして、ΦX固有ベクトルの定義に従って次のように分解することができる。
f:id:deeptoneworks:20161017161752p:plain
固有ベクトルの定義)
f:id:deeptoneworks:20161020231606p:plain

ここで、EはΦX固有ベクトルを列ベクトルに持つ行列、Dは固有値を対角成分に持つ対角行列。

更に、Eが直交行列であることから、
f:id:deeptoneworks:20161018052705p:plain
と書ける。

これと、先の変形した式を用いると
f:id:deeptoneworks:20161017212508p:plain
とPを表すことができる。
(ここ、変形手順がわかりません・・・)

ただし、
・QはPと同じサイズの任意の直交行列
・D-1/2はDの対角成分を-1/2乗したもの

Qを定める

Qは任意に与えることができますが、以下の2つを与えることが多いようです。

PCA白色化

Qを単位行列にした時の線形変換P
f:id:deeptoneworks:20161017213234p:plain

共有分散行列の固有ベクトルを利用することから主成分分析に似ているため、PCA白色化とよぶ。

ZCA白色化

QをEにした時の線形変換P
Pが対称行列になっていることから、ゼロ位相白色化(ZCA白色化)とよぶ。
f:id:deeptoneworks:20161017220707p:plain

PCA白色化,ZCA白色化どちらの場合でも、データによっては特定の成分の分散が0かとても小さい(Dの対角成分のどこかが0,もしくはとても小さい)ことがある。
この場合、D-1/2を計算する際に問題になるので、とても小さい値ε(例えば10^-6など)をDに加える。
f:id:deeptoneworks:20161017221147p:plain

PCA白色化とZCA白色化の違い

以下はPCA白色化とZCA白色化の射影行列Pの行ベクトルを画像化したもの
(引用:dimensionality reduction - What is the difference between ZCA whitening and PCA whitening? - Cross Validated)

f:id:deeptoneworks:20161017224126p:plain

ZCA白色化ではフィルタ部分に相当する画素とその他の部分の画素の差を強調するようになっている。 (オンセンタと呼ばれる)
一方,PCA白色化では画像の高周波成分を強調するようになっている。元の画像の見た目とは全く違うものになる。

以下の画像をPCA白色化,ZCA白色化したものを示す。
f:id:deeptoneworks:20161017225917p:plain
(元画像)

f:id:deeptoneworks:20161017230128p:plain
(PCA白色化)

f:id:deeptoneworks:20161017230200p:plain
(ZCA白色化)

白色化の具体的な用途

基本的な機械学習アルゴリズムでは、白色化の有無によって結果が変わるようなものではない。
単位超球上に射影したデータの高密度方向を検出する場合のように、データの分布の方向のみが問題になる場合によく利用される。

更に,ZCA白色化が有効に機能する例はAEやCNNの前処理である。
ZCA白色化を施すことによってより局所的,鮮鋭的な特徴を学習することができる。 (多分)

(参考) stats.stackexchange.com

補足

対角行列

対角成分以外が0の正方行列

共分散行列

対角成分に分散、それ以外の部分に共分散が並ぶ行列

直交行列

転置行列と逆行列が等しくなる正方行列。 MT M = M MT = Eが成り立つ。

固有値固有ベクトル

正方行列Aに対して f:id:deeptoneworks:20161018033521p:plain が成り立つ時xを固有ベクトル,λを固有値という
固有ベクトルはあるベクトルAを方向は変えず大きさだけ変えるような線形変換を施すベクトル。
固有値はその変換の倍率を表している。

React x flux でツールを作ったのでfluxについてまとめる

flux

そこら中で貼られてるfluxの概念図を貼っておきます。

f:id:deeptoneworks:20161012135719p:plain

簡単に言うと、
・Viewは親Component
・Actionは処理の内容を記したオブジェクト
・DispatcherはStoreへActionの通知を行う。具体的には登録されたCallbackを順次実行していく。
・Storeは状態の保持と状態の更新の通知を行う

View -> Action -> Dispatcher -> Storeとデータが伝搬されていく。
ViewはStoreを保持しており、Storeの状態が更新されたらViewに通知をする。

では、ひとつひとつの登場人物を詳しく見ていきます。

Dispatcher

DispatcherはActionを受け取って、登録されているCallbackを実行するものです。
Dispatcherに必要なメソッドはfluxライブラリから全て提供されていますので、私たちにに必要なことはDispatcherを使うことだけです。
使うというのは、DispatcherにCallbackを登録、Callbackを実行、Callbackが呼び出される順番を操作する、などといったことを指します。

import {Dispatcher} from "flux";
export default new Dispatcher();

Dispatcherはしばしば拡張されることがあります。
拡張にどのようなパターンがあるのかはわかりませんが、1つのパターンとして、どのVIewからdispatchされたか、という情報を付与するために用いられます。

import {Dispatcher} from "flux";
import assign from ‘object-assign’;
const dispatcher = assign(new Dispatcher(),{
    handleViewAAction: function (action) {
        this.dispatch({
            source: "viewA",
            action: action
        })
    },
    handleViewBAction: function (action) {
        this.dispatch({
            source: "viewB",
            action: action
        });
    }
});
export default dispatcher;

ViewからActionを実行するときは、dispatchメソッドの代わりに、先に拡張したメソッドを用います。
拡張していない場合は普通にdispatchメソッドを使います。

dispatcher.handleViewBAction(action);

こうすることで、どのViewからDispatchされたか、という情報を付与した上でCallbackを実行できます。
他にも拡張のパターンがあれば教えて下さい><

Action

Actionは処理の内容を記したオブジェクトをDispatcherにdispatch(発行)します。
dispatchされたらDispatcherは登録されたCallbackを実行する、という感じですね。

import dispatcher from "./dispatcher";
const Action = {
    sampleAction(data){
        dispatcher.dispatch({type:"SAMPLE_ACTION",value:data})
    }
};
export default Action;

actionを受け取るCallbackはtypeを見てデータをどう扱うかを決定します。

Store

ストアは状態を保持します。
更に、dispatcherにCallbackを登録する役割もあります。
同じstoreが複数同じCallbackを登録してはいけないので、Storeはシングルトンになります。

import { EventEmitter } from "events";
import dispatcher from "./dispatcher";
class SampleStore extends EventEmitter {
    constructor() {
        super();
        this.data = 0;
        this.token = dispatcher.register(this.handleAction.bind(this));
    }
    getAll(){
        return this.data;
    }
    emitChange(){
        this.emit("change");
    }
    addChangeListener(callback){
        this.on("change",callback);
    }
    handleAction(action) {
        switch (action.type) {
            case "SAMPLE_ACTION": {
                this.data = action.value;
                this.emit(‘change’);
            }
        }
    }
}
export default new SampleStore();

コンストラクタでdispatcherにCallbackを登録しています。このメソッドにactionが届くわけですね。

さて、登録したCallback、handleActionにactionが届きます。
この時storeはactionの型を見て、どのようにデータを扱うかを判断します。
上記の例では単純にdataにactionのvalueを入れているだけですね。
そして、データに変更があったという事をEventEmitterにより通知しています。
この通知をViewが受け取って、状態が更新されていきます。

addChangeListener(callback){
    this.on("change",callback);
}

例えば上記の例ですと、changeイベントが通知されればaddChangeListenerに登録されたcallbackが実行されるといった流れです。

View

Viewではまず、storeの初期値をコンストラクタで受け取ります。
一般にstoreではgetAllメソッドという名前で提供することが多いようです。

this.state = store.getAll();

そして、storeからデータの変更通知を受け取るcallbackを登録します。

let self = this;
store.addChangeListener(function(){
    self.setState(store.getAll());
});

これで、storeの値が変更されると、viewのstatusも変更されるようになりました。
あとはこのstatusの値を子Componentのpropsとして流し込んでやれば単一方向のデータフローが実現できますね。

セルの素性を抽出する

タイトルのセルか、データのセルか、列見出しか行見出しか・・などを推定するための素性を設定するで。

とりあえず、以下にしたで。
x座標,y座標の算出に使うセルの原点は左上としてるで。

  • width
  • height
  • x
  • y
  • x座標(%)
  • y座標(%)
  • 面積
  • 面積(%)
  • セルの4隅の座標
  • セルの文字の種類
  • セルの文字そのもの

[width,height]
セルの横幅と縦幅。何も整形しない。

[x,y]
横から何個目、縦から何個目といった値。
問題点があって、認識されないような小さいセルは無視するから実際のものとxがズレるんやなあ。yはズレへんけど。
あと、エクセル画像が2枚以上の時は、1枚目の最後のyを引き継ぐで。

[x座標,y座標(%)]
セルの左上の座標をwidth,heightで割った値。 これはちょっとした整形処理を行ってるんや。
例えば、3枚のエクセル画像があって、それぞれ100%,30%,100%埋まってるとする。つまり、2枚目は下2/3が空白の画像って事。
このとき、100%,100%,30%と解釈して、y座標は一番下のセルまでが100%として算出されるで! だから、一番下のセルは画像に空白があっても絶対99%とかいう値になるって事や!
ええ感じやな!

[面積]
これはセルの面積そのままな。

[面積(%)]
これはセルの面積を、すべての画像の面積で割った値や。めっちゃ小さい値になるわ。

[セルの4隅の座標]
これもy座標算出のときと同じように、白紙のスペースは埋める処理をやっとるで。
それ以外の整形はしてないで。

[セルの文字の種類]
空白か文字か数字か、やな。

どの素性が分類に活きてくるかはわからんから、とりあえず思いつくものは全部とっといたで。 もっと他にも良さげな素性ないかなあ・・。なんかある気がするんよな。