2017/09/02

数字フォントでモデル学習

せっかく数字フォントで画像をつくったので、それでニューラルネットワークモデルを学習させてみます。
そのモデルで手書き数字のMNISTをどれだけ学習できるか、についても見てみます。

モデルの学習にはKerasを使います。コードは以下の通り。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
# This script trains a neural network model from font-based number images.
# The trained model is evaluated by validation data of MNIST.
import gc, keras, os, re
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Reshape
from keras.layers.convolutional import Conv2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers import Flatten, Dropout
from keras.datasets import mnist
from PIL import Image, ImageDraw, ImageFont # For drawing font images

FONT_DIR = "C:/Windows/Fonts"
TRAIN_EPOCH = 30

# Image size is the same as MNIST
IMAGE_WIDTH = 28
IMAGE_HEIGHT = 28

# Make images and labels of 0-9 by using the font specified by font_name
def make_image(font_name):
    font = ImageFont.truetype(FONT_DIR+"/{}".format(font_name), 25)
    images = np.empty((0, IMAGE_WIDTH, IMAGE_HEIGHT))
    labels = np.empty((0, 1))
    for i in range(10): # Draw 0-9 as image
        image = Image.new('RGB', (IMAGE_WIDTH, IMAGE_HEIGHT), (0, 0, 0))
        draw = ImageDraw.Draw(image)
        font_width, font_height = font.getsize(str(i))
        draw.text(((IMAGE_WIDTH-font_width)/2, (IMAGE_HEIGHT-font_height)/2),
                  str(i), font=font, fill=(255, 255, 255))
        ni = np.delete(np.asarray(image), [1, 2], 2) # Remove green and blue
        ni = ni.reshape(1, ni.shape[0], ni.shape[1]) # Convert to 1x28x28 matrix
        images = np.append(images, ni, axis=0)
        labels = np.append(labels, np.array([i]).reshape(1, 1), axis=0)
    return images, labels

# Collect images of numbers for all fonts
def make_images():
    # Collect only true type fonts including numbers
    rp = re.compile(".*ttf")
    font_list = [f for f in os.listdir(FONT_DIR) if rp.match(f)
                 and (f != "webdings.ttf" and f != "wingding.ttf" and
                      f != "marlett.ttf" and f != "opens___.ttf" and
                      f != "symbol.ttf")]
    images = np.empty((0, IMAGE_WIDTH, IMAGE_HEIGHT))
    labels = np.empty((0, 1))
    for font_name in font_list:
        fimages, flabels = make_image(font_name)
        images = np.append(images, fimages, axis=0)
        labels = np.append(labels, flabels, axis=0)
    return images, labels

# Define a discriminator model for numbers
def num_discriminator_model():
    model = Sequential()
    model.add(Conv2D(64, (5, 5), strides=(2, 2), padding='same',
                     input_shape=(IMAGE_WIDTH, IMAGE_HEIGHT, 1), 
                     data_format='channels_last'))
    model.add(LeakyReLU(0.2))
    model.add(Conv2D(128, (5, 5), strides=(2, 2), data_format='channels_last'))
    model.add(LeakyReLU(0.2))
    model.add(Flatten())
    model.add(Dense(256))
    model.add(LeakyReLU(0.2))
    model.add(Dropout(0.5))
    model.add(Dense(10))
    model.add(Activation('softmax'))
    print(model.summary())
    return model

if __name__ == '__main__':
    # Make images of numbers from fonts
    x_train, y_train = make_images()
    x_train = (x_train.astype(np.float32) - 127.5)/127.5 # [0,255] --> [-1,1]
    x_train = x_train.reshape((x_train.shape[0], x_train.shape[1], x_train.shape[2], 1))

    # Load MNIST data for validation
    (_, _), (x_val, y_val) = mnist.load_data()
    x_val = (x_val.astype(np.float32) - 127.5)/127.5 # [0,255] --> [-1,1]
    x_val = x_val.reshape((x_val.shape[0], x_val.shape[1], x_val.shape[2], 1))

    # Encode labels into 1-hot vectors
    y_train = keras.utils.to_categorical(y_train, num_classes=10)
    y_val = keras.utils.to_categorical(y_val, num_classes=10)

    # Make, compile and train a model
    model = num_discriminator_model()
    model.compile(optimizer='rmsprop',
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])
    hist = model.fit(x_train, y_train, validation_data=(x_val, y_val),
                     epochs=TRAIN_EPOCH, batch_size=32).history

    # Evaluate the model by train and validation data
    score = model.evaluate(x_train, y_train, batch_size=32)
    print("\ntrain acc : ", score)
    score = model.evaluate(x_val, y_val, batch_size=32)
    print("\nmnist val acc : ", score)

    # Write training history into a file
    f = open("history.dat", mode="w")
    f.write("#epoch train-loss train-acc val-loss val-acc\n")
    for v in range(0, TRAIN_EPOCH):
        f.write("{0} {1:10.6f} {2:10.6f} {3:10.6f} {4:10.6f}\n"
                .format(v, hist["loss"][v], hist["acc"][v],
                        hist["val_loss"][v], hist["val_acc"][v]))
    f.close()
    gc.collect() # To suppress error messages of TensorFlow

乱数の影響で実行するたびに精度は変わりますが、
LossAccuracy
Train0.002670.99852
MNIST6.101930.52960
となりました。全然ダメですね。MNISTでの精度が53%しかありません。

学習中のLossの変化が下図です。

1エポックあたりのサンプル数は3380個です。図を描くときにEpochの値が1だけ左にずれてしまいましたが、学習が進むにつれてMNIST側のLossがどんどん増えていることがわかります。

学習中のAccuracyの変化が下図です。

Trainの精度は高いですが、MNISTに対しては最初に少しよくなるものの、50%前後から進みません。 やはり、同じ数字画像であっても、傾向の異なるデータではうまく学習できませんでした。

数字フォントの画像化

システムにインストールされているフォントを使って数字を描画し、jupyter notebookで表示します。
次のコードをjupyter notebookに貼り付けて実行すると、 色々なフォントの画像が生成されます。

数字フォントを眺めたい場合にどうぞ。
(You can see images of numbers 0-9 for all fonts in your system by executing the code below in jupyter notebook)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
%matplotlib inline
import os
import re
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw, ImageFont

fontdir = "C:/Windows/Fonts"
width = 28
height = 28
def make_image(font_name):
    font = ImageFont.truetype(fontdir+"/{}".format(font_name), 25)
    for i in range(10): # Draw 0-9 as image
        image = Image.new('RGB', (width, height), (0, 0, 0))
        draw = ImageDraw.Draw(image) 
        font_width, font_height = font.getsize(str(i))
        draw.text(((width-font_width)/2, (height-font_height)/2),
                  str(i), font=font, fill=(255, 255, 255))
        ax = plt.subplot(1, 10, i+1) # Horizontally align images
        ax.tick_params(labelleft='off', labelbottom='off',
                       left='off', bottom='off') # Remove ticks
        plt.imshow(np.asarray(image))
    plt.show() # Finish drawing an image and output it

# Collect only true type fonts including numbers
rp = re.compile(".*ttf")
font_list = [f for f in os.listdir(fontdir) if rp.match(f)
             and (f!="webdings.ttf" and f!="wingding.ttf")]
for font_name in font_list:
    print(font_name)
    make_image(font_name)
http://fushime2.hatenablog.com/entry/2017/01/13/193844
を参考に作成しました。

2017/07/30

音の周波数と場所を表すニューロンは共有されている

海馬のニューロンの一部が物理的空間内の位置を表現していることは既に知られている。
今回、ラットを使った[2]の実験により、音の周波数を表現しているニューロンが、空間を表すニューロンと重複していることが分かった。つまり、空間的な位置を覚えないといけないタスクをこなす時と、音の周波数を覚えないといけないタスクをこなす時とで、共通のニューロンのネットワークが利用されているということである(解説記事は[1])。

実験では、ラットはジョイスティックを操作して音を鳴らし、ジョイスティックを放したときに音が特定の周波数の範囲に入っていたら水が報酬として与えられる。なお、ジョイスティックを押した時間に応じてニューロンが反応している可能性を排除するために、押している間の周波数の変化速度を変動させて実験している。


空間と音とで共有できているなら、他の何かとも共有している可能性はある。そうなると、そもそも海馬内のこのネットワークは何を符号化しているのだろう…。

-- 参考文献 --
[1] https://www.nature.com/nature/journal/v543/n7647/full/543631a.html (doi:10.1038/543631a)
[2] https://www.nature.com/nature/journal/v543/n7647/full/nature21692.html (doi:10.1038/nature21692)

2017/07/29

DCGAN で数字画像を生成

色々なところでGenerative Adversarial Network (GAN) が使われているということなので、GANの初心者向け解説である
https://elix-tech.github.io/ja/2017/02/06/gan.html
を参考にして数字画像生成を試してみました。

試したのは、GANの中でもCNNを使うDeep Convolutional GAN (DCGAN) です。
同解説の中でコードを含めて解説されているのでほぼそのまま使いました。
学習に用いるデータはおなじみのMNISTです。
ただし、Keras 2.0.6 を用いたため、少し修正が必要になりました。

修正後のコードは

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from keras.models import Sequential
from keras.layers import Dense, Activation, Reshape
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import UpSampling2D, Conv2D

# For discriminator
from keras.layers.advanced_activations import LeakyReLU
from keras.layers import Flatten, Dropout

def generator_model():
    model = Sequential()
    model.add(Dense(1024, input_dim=100))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Dense(128*7*7))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Reshape((128, 7, 7), input_shape=(128*7*7,)))
    model.add(UpSampling2D((2, 2), data_format='channels_first'))
    model.add(Conv2D(64, (5, 5), padding='same', data_format='channels_first'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(UpSampling2D((2, 2), data_format='channels_first'))
    model.add(Conv2D(1, (5, 5), padding='same', data_format='channels_first'))
    model.add(Activation('tanh'))
    print(model.summary())
    return model

def discriminator_model():
    model = Sequential()
    model.add(Conv2D(64, (5, 5), strides=(2, 2), padding='same', input_shape=(1, 28, 28), 
                     data_format='channels_first'))
    model.add(LeakyReLU(0.2))
    model.add(Conv2D(128, (5, 5), strides=(2, 2), data_format='channels_first'))
    model.add(LeakyReLU(0.2))
    model.add(Flatten())
    model.add(Dense(256))
    model.add(LeakyReLU(0.2))
    model.add(Dropout(0.5))
    model.add(Dense(1))
    model.add(Activation('sigmoid'))
    print(model.summary())
    return model
となります。これ以外の部分のコードは、同解説を参照ください。

5エポックまで学習を進めてみたところ、うまく文字が生成できる場合と、全く文字を生成できない場合がありました。文字が生成できる場合がSuccess{1,2,3}で、出来ない場合がFailです。

Epoch=0, Batch=0

Epoch=0, Batch=500

Epoch=0, Batch=1000

Epoch=0, Batch=1500

Epoch=1, Batch=0

Epoch=2, Batch=0

Epoch=3, Batch=0

Epoch=4, Batch=0

Epoch=5, Batch=0




Lossは次の図のようになりました。バッチサイズは32で、1エポックで実行されるバッチの総数は1875です。バッチごとにプロットしています。




初期値に大きく依存しているようで、全く同じように実行しても成功したり失敗したりします。安定して画像が生成できるようにするためにはもう少し工夫が必要なようです。

また、こうやって単純に画像を生成すると、生成した画像にラベルが付かないので、MNISTの学習データを増やす目的には使えません。ラベルと画像をペアで生成できればいいのですが、この点についてももう少し工夫が必要そうです。

脳の過成長と自閉症スペクトラム障害に関連が

生後6ヶ月から12ヶ月の間に起きる大脳皮質の表面積の過拡張が
自閉症スペクトラム障害(ASD)の発生と関連していることが報告された[1]。

遺伝的なリスクが高く、生後24ヶ月でASDと判定された乳児(HR-ASD)が15名、
遺伝的なリスクが高いが生後24ヶ月でASDと判定されなかった乳児(HR-neg)が91名、
遺伝的なリスクが低く、生後24ヶ月でASDと判定されなかった乳児(LR)が42名、
のデータで評価が行われた。

脳の表面積の計測にはMRIが用いられ、6ヶ月目、12ヶ月目、24ヶ月目の3回計測された。
測定結果のグラフ([1]のFigure 1)を見ると、結構ばらつきが大きいように見えるが、
6ヶ月目から12ヶ月目までの表面積の成長率がHR-negに対してHR-ASDのほうがP=0.04で
大きく、LRに対してはP=0.01で大きいという結果が得られたとのこと。

また、6ヶ月目と12ヶ月目のMRIから得られる表面積からディープラーニングを使って
24ヶ月目にASDと判定されるかどうかを予測したところ、8割くらい当てられたそうだ。

ここで使われたディープラーニングの手法はオートエンコーダで、
[2]で示されている方法を用いている。

医療分野の研究にもディープラーニングが用いられるようになってきていることが分かる。


-- 参考文献 --
[1] http://www.nature.com/nature/journal/v542/n7641/full/nature21369.html (doi:10.1038/nature21369)
[2] http://science.sciencemag.org/content/313/5786/504 (DOI: 10.1126/science.1127647)

2017/07/28

Proxima b まで探査機を送る計画

地球サイズの惑星 Proxima b まで探査機を送る計画があるそうだ[1]。
YouTubeの動画は[2]にある。
レーザーで光速の20%まで探査機を加速することで21.1年で到達する計算とのこと
(減速なしで最初の加速時間は無視するという仮定で計算)。


それはともかく、同記事の図を見るとオールトの雲は太陽からおおよそ1光年の距離にある。
一方、Proxima bは4.22光年の距離にある。Proxima bが回っている地球にとっての太陽は
Proxima Centauriだが、これにも同様のオールトの雲があり、距離も同じだと仮定すると、
太陽から見て、3.22光年あたりに分布していることになる。
そうするとオールトの雲同士の距離は2.22光年しか離れていないということになる。

[太陽] --(1光年)--[オールトの雲]------(2.22光年)------[オールトの雲]--(1光年)--[P.C.]

実際には遠いのだが、このように考えるとお隣の恒星が意外と近く感じるのではないだろうか。



-- 参考文献 --
[1] https://www.nature.com/news/what-it-would-take-to-reach-the-stars-1.21402 (doi:10.1038/542020a)
[2] https://www.youtube.com/watch?v=zVySM1oofmY

天文学も GAN を活用

天文学でもGenerative Adversarial Networks (GANs)を利用して
重力レンズで歪んだ銀河を生成し、それらを検出するソフトウェアの
性能を向上させようとしているそうだ[1]。

しかしながら、GANで生成した画像はいい感じのときもあれば、
奇妙な画像を生成することもあるわけで、芸術ならともかく
基礎科学の分野で使うなら十分に注意深く使う必要があるだろう。


-- 参考文献 --
[1] https://www.nature.com/news/astronomers-explore-uses-for-ai-generated-images-1.21398 (doi:10.1038/542016a)

2017/07/23

良くくっつく接着パッチ

乾燥してても、湿っていても、水中でも、油中でも良くくっつく接着パッチができたとのこと[1, 2, 3]。

タコの吸盤っぽい形にすることで実現できた模様。
何度も繰り返し使え、吸盤からの汚染もない。

安く簡単に手に入るようになる日は来るのだろうか。
普通の吸盤は時間とともに剥がれてしまうので、
日用品に使われるようになれば、とても便利になりそうだ。


-- 参考文献 --
[1] http://www.natureasia.com/ja-jp/nature/highlights/86698
[2] http://www.nature.com/nature/journal/v546/n7658/full/546358a.html (doi:10.1038/546358a)
[3] http://www.nature.com/nature/journal/v546/n7658/full/nature22382.html (doi:10.1038/nature22382)

暗黒物質と暗黒エネルギー

暗黒物質と暗黒エネルギーについての2016年のまとめ記事。
http://www.nature.com/nature/outlook/dark-universe/index.html

4つの大きな疑問点が
http://www.nature.com/nature/journal/v537/n7622_supp/full/537S206a.html (doi:10.1038/537S206a)
に挙げられています。

1. 暗黒物質の粒子は存在するの?
2. 暗黒物質はどんなものとも相互作用するの?
3. 宇宙定数で暗黒エネルギーを説明できるの?
4. 最終的に宇宙に何が起きるの?

10年以上前からある疑問で、まだ解決していないということですね。
詳細はリンク先を参照。

人工ブラックホールでホーキング輻射

(2016年の記事より)

光の代わりに音波を使ってホーキング輻射を検証するための人工ブラックホールをつくり、
ホーキング輻射を観測したとのこと[1, 2]。
人工的に本物のブラックホールが作られたわけではない。


-- 参考文献 --
[1] https://www.nature.com/news/artificial-black-hole-creates-its-own-version-of-hawking-radiation-1.20430 (doi:10.1038/536258a)
[2] http://www.nature.com/news/one-man-band-the-solo-physicist-who-models-black-holes-in-sound-1.20437 (doi:10.1038/nature.2016.20437)

脳から情報を読み取り自分の足を動かす

(2016年の記事より)

脊髄損傷により片足が麻痺したアカゲザルの足を、損傷部分をスキップする経路を作ることで
自由に歩けるようにできたとのこと[1, 2]。

おおまかな方法は次の通り
1. 脳に96チャネルのマイクロアレイを埋め込んで、そこから得られた信号を適切に変換する。
2. 変換後の信号をパルス生成器にワイヤレスで転送する。
3. 転送された信号に基づいて電気信号をパルス生成器が作る。
4. 腰の辺りの脊髄に埋め込んだ電極により電気信号を神経に伝達する。
5. 足が動く。

-- 参考文献 --
[1] http://www.nature.com/nature/journal/v539/n7628/full/539177a.html (doi:10.1038/539177a)
[2] http://www.nature.com/nature/journal/v539/n7628/abs/nature20118.html (doi:10.1038/nature20118)

地球サイズの惑星が近くの恒星で発見

(2016年の記事より)

地球から1.3パーセクの距離にあるProxima Centauriの周りを回っている惑星が発見された[1, 2, 3, 4]。
Proxima Centauri b (またはProxima b)と名づけられたその惑星の質量は地球の1.3倍とのこと。公転周期は11.2日。
ただし、Proxima Centauriは強力なフレアを放出している。
さらに、Proxima Centauri bが受けるX線の量も地球の400倍と大きい。
このため、大気が存在するかは今のところ分かっていない。


-- 参考文献 --
[1] http://www.natureasia.com/ja-jp/nature/highlights/77976
[2] https://www.nature.com/news/earth-sized-planet-around-nearby-star-is-astronomy-dream-come-true-1.20445
[3] https://www.nature.com/nature/journal/v536/n7617/full/536408a.html
[4] https://www.nature.com/nature/journal/v536/n7617/full/nature19106.html (doi:10.1038/nature19106)

2017/06/11

マウスの脳全体を1個で包み込むニューロン

マウスの脳全体を1つだけで包み込むように枝を伸ばしているニューロンがみつかったとのこと(日本語は[1]、英語は[2])。
そのニューロンは、「前障 [3]」と呼ばれる場所に位置している。


記事に書かれている以上の情報が得られないかと探してみたが、見つからず、断念。

会議自体は、The Brain Research through Advancing Innovative Neurotechnologies (BRAIN) Initiative の2017年2月15日にメリーランド州で開かれ、Christof Kochにより発表されたらしいのだが、会議の情報が見当たらない。

http://www.braininitiative.org/
を探しても、これに対応する発表関連資料が見つからない。
それどころか、News(http://www.braininitiative.org/news/)からnatureの記事へリンクが張られている。

http://www.alleninstitute.org/
にもなし。

-- 参考文献 --
[1] doi:10.1038/ndigest.2017.170615
[2] doi:10.1038/nature.2017.21539
[3] https://ja.wikipedia.org/wiki/%E5%89%8D%E9%9A%9C

2017/06/07

Raspberry Pi で温度測定

ラズパイマガジン2016年6月号と2017年2月号をみながら、
温度計を作成してみました。

回路と測定用のコードは、2017年2月号 p.21の下側(26番)そのままに作成しました。
コードはラズパイマガジン2017年2月号の読者限定サイトからダウンロードしてください
(書籍を購入すればわかります)。

使用したサーミスタは103AT-2で、[1]によると、25℃での抵抗値が10kΩ、B定数が3435K。

Wikipediaのサーミスタの説明[2]から、サーミスタの抵抗値Rから温度を求める式は、
\[\frac{1}{T} = \frac{1}{T_0} + \frac{1}{B}*\log(\frac{R}{R_0}) \ \ \ \ \ \cdots 式(1) \] となります。上記サーミスタの場合、\(T_0=25\)℃(=298K)、\(R_0=10000\)Ω、B=3435Kとなります。

ADコンバータは電圧をデジタル値に変換してくれるので、得られる電圧から抵抗値に
変換します。

[電源3.3V]→[サーミスタ]→*→[抵抗10kΩ]→[GND]
でつないでおり、抵抗の前後の電位差\(V_f\)が得られます(*の場所で測定、下の写真の緑色の線を参照)。
サーミスタ前後の電位差を\(V_s\)とすると、 \[V_s+V_f=3.3\] \[\frac{V_s}{R_s}=\frac{V_f}{R_f}\] より、 \[R_s=\frac{R_f}{V_f}(3.3-V_f)\] となります。(式1)の\(R\ \)に\(R_s\)を代入すると、測定値\(V_f\)から温度\(T\ \)が得られます。
なお、計算時の温度の単位はK(ケルビン)です。

以下は、作った回路の写真です(サーミスタにはピントが合っていないのでぼやけています)。


--- 参考URL ---
[1] http://akizukidenshi.com/catalog/g/gP-07258/
[2] https://ja.wikipedia.org/wiki/%E3%82%B5%E3%83%BC%E3%83%9F%E3%82%B9%E3%82%BF

2017/05/28

Raspberry Pi でLEDを点滅させる

Raspberry Piと電子工作キットを手に入れたので、
ラズパイマガジン2017年2月号 p.14-15を参照しながら、LEDを光らせてみます。

最初に、ライブラリをインストールします。

sudo apt-get install libi2c-dev


次に、GPIOの操作に必要となるpythonのライブラリをインストールします。
$ pip install wiringpi

もし、インストール中に次のエラー
error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1
が出力される場合は、
$ sudo apt-get install python-dev
を実行した後に、再度、pip install wiringpi を実行してください。


LEDをゆっくり点滅させるコードは次の通りです。
[test.py]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import wiringpi as pi
import time
LED_PIN=14
pi.wiringPiSetupGpio()
pi.pinMode(LED_PIN, pi.OUTPUT)
pi.softPwmCreate(LED_PIN, 0, 11)
for j in range(0,2):
    for i in range(1,11):
        pi.softPwmWrite(LED_PIN, i)
        time.sleep(0.1)

    for i in reversed(range(1,11)):
        pi.softPwmWrite(LED_PIN, i)
        time.sleep(0.1)

pi.digitalWrite(LED_PIN, pi.LOW)


これを
$ sudo python test.py
で実行すると、LEDが点滅します。

回路は次の写真のようにしました。

2017/04/24

地球の酸素イオンが月まで届く

地球の酸素イオン(O+イオン)が月まで届いているという観測結果が得られたそうだ。
日本の探査機「かぐや」の観測データに基づいた研究とのこと。

著者のインタビュー記事はここ
http://www.natureasia.com/ja-jp/natastron/interview/contents/1

論文はここ
https://www.nature.com/articles/s41550-016-0026

2017/03/20

初期の銀河にはダークマターが少ない

[1,2,3]によると、今の銀河にあるダークマターの質量に比べて
昔の銀河のダークマターの質量が少ない観測結果が得られたとのこと。
著者らが観測した銀河の赤方偏移zは0.9, 1.5, 1.6, 2.2, 2.2, 2.4。
距離はおおよそ100億光年(※1)。

現在の銀河はダークマターの影響により、中心部から一定距離離れると、
回転速度がおおよそ一定になることが知られている。
一方、昔の銀河は中心部から一定距離離れると回転速度が
低下していくことが今回観測された。

銀河の質量に占めるバリオンの量が多いと今回の観測結果が得られることから
ダークマターの割合が少ないことが分かる。


(※1) zからの距離換算は例えば、
http://www.asj.or.jp/geppou/archive_open/2003/pdf/200312art5.pdf
の図4を見ればよい。

参考文献
[1] R. Genzel et al., "Strongly baryon-dominated disk galaxies
at the peak of galaxy formation ten billion years ago," doi:10.1038/nature21685, 2017
[2] M. Swinbank, "Distant galaxies lack dark matter," Nature 543, 318-319, 2017
(doi:10.1038/543318a) ([1]の解説記事)
[3] http://www.natureasia.com/ja-jp/nature/pr-highlights/11726 ([1]の解説記事)

2017/03/06

第二言語の習得能力と遺伝

[1]によると、「第二言語の習得能力は遺伝の影響が強い」そうです。

残念ながら、この根拠となる「一卵性双生児を用いた慎重な調査」の
文献のリファレンスが記載されておらず、真偽はよくわかりません。

それっぽいものは見つけられませんでしたが、[2]経由で見つけた[3]によると、
「Statistical modeling revealed that subjects' grades in the language immersion program were best predicted by fractional anisotropy and COMT genotype.」
とのことですので、第二言語の習得能力はCOMT遺伝子に影響を受けるようです。


参考文献
[1] 池谷裕二, "怖いくらい通じるカタカナ英語の法則," ISBN978-4-06-257987-2, 2016
[2] http://www.kagaku-kentei.jp/news_detail/data/278
[3] Ping C. Mamiya, Todd L. Richards, Bradley P. Coe, Evan E. Eichler, and Patricia K. Kuhl, "Brain white matter structure and COMT gene are linked to second-language learning in adults," PNAS 2016 113 (26) 7249-7254, published ahead of print June 13, 2016, doi:10.1073/pnas.1606602113

2017/02/25

LSTMで文字列を単語(番号)にマッピング

LSTMを無意味に使ってみました。

目的は、文字列を入力したら単語が出力されるニューラルネットワーク(NN)を作ることです。
単なるマッピングなので、連想配列を使えば解決ですが、無理やりNNで実現してみます。

入力層は、文字のone-hotベクトルとします。
同じく、出力層は単語のone-hotベクトルとします。

学習データはどこからともなく見つけてきた英単語300個。

入力と出力の長さが異なっているので、そろえるためにブランク記号 _ を使います。

例えば、n e u r o n を入力すると、_ _ _ _ _ neuron が出力されるように学習します。
単語ごとに文字列長が異なるので、足りない分は入力側にも _ を使います。

単語リストは

business
company
...
neuron
というふうに、単に1行に1単語が書かれたファイルです。

このファイルを次のスクリプトの第1引数に指定してスクリプトを実行します。
学習用ライブラリにはTensorFlowベースのKerasを利用しました。

ブランク記号 _ には数値で0を、ASCII文字には1から順に整数を割り当てています。
単語についても同じです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import numpy as np
import sys,os
import numpy.random as random
from keras.models import Sequential
from keras.layers import Dense, Activation, LSTM, TimeDistributedDense
from keras.optimizers import RMSprop
import keras.preprocessing.text as pp

if len(sys.argv) != 2:
    print("Usage: seq2seq.py [word list]")
    exit(0)

words_file = sys.argv[1]
if not os.path.isfile(words_file):
    print(words_file+" is not found.")
    exit(0) 

random.seed(123)

# 単語データの読み込み
words = []
max_ch_seq = 0
for line in open(words_file):
    line = line.rstrip('\n')
    chars = [ord(c)-ord('a')+1 for c in list(line)]
    col = np.array(chars)
    max_ch_seq = max(max_ch_seq, len(col))
    words.append([col, line])

# 文字長を揃える
max_ch_seq_orig = max_ch_seq
for w in words:
    while len(w[0]) < max_ch_seq:
        w[0] = np.append(w[0], 0) # 0はブランク

word_id_map = {}
id = 1
for w in words:
    word_id_map[w[1]] = id # 重複がないことが前提
    id += 1

input_vec_len = ord('z') - ord('a') + 2 # +1はブランク入力のため
output_vec_len = len(words) + 1 # +1はブランク出力のため
np.set_printoptions(threshold=np.inf)
for w in words:
    # 入力(=文字)のone-hotベクトルを作成する
    input_vec_seq = np.zeros((len(w[0]), input_vec_len))
    input_vec_seq[np.arange(len(w[0])), w[0]] = 1
    w.append(input_vec_seq)

    # 出力(=単語)のone-hotベクトルを作成する
    x = np.zeros(len(w[0]), dtype=int)
    x[-1] = word_id_map[w[1]] #最後に単語を入れる
    output_vec_seq = np.zeros((len(w[0]),output_vec_len))
    output_vec_seq[np.arange(len(w[0])), x] = 1
    w.append(output_vec_seq)

# この時点で、wordsの各要素は、
#   (文字列(数字), 単語, 文字列のOne-hotベクトル, 単語のOne-hotベクトル)
# となっている。
data_dup = 1  # 何度も同じデータを入れるなら、2以上にする

# 訓練用データを設定する
x_train = np.empty((len(words)*data_dup, max_ch_seq, input_vec_len))
y_train = np.empty((len(words)*data_dup, max_ch_seq, output_vec_len))
for j in range(0, data_dup):
    for i in range(0, len(words)):
        x_train[i+len(words)*j] = words[i][2]
        y_train[i+len(words)*j] = words[i][3]

# 評価用データを設定する
x_val = np.empty((len(words), max_ch_seq, input_vec_len))
y_val = np.empty((len(words), max_ch_seq, output_vec_len))
for i in range(0, len(words)):
    x_val[i] = words[i][2]
    y_val[i] = words[i][3]

# モデルを作る
batch_size = 50
model = Sequential()
model.add(LSTM(16, return_sequences=True,
               batch_input_shape=(batch_size, max_ch_seq, input_vec_len)))
model.add(LSTM(16, return_sequences=True))
model.add(TimeDistributedDense(128))
model.add(TimeDistributedDense(output_vec_len, activation='softmax'))

# 学習する
rmsprop = RMSprop(lr=0.0005, rho=0.9, epsilon=1e-08)
model.compile(loss='categorical_crossentropy', optimizer=rmsprop, metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=batch_size, nb_epoch=5000, validation_data=(x_val, y_val))

# モデルを保存
model.save_weights('model.dat')

# 結果の確認
r = model.predict_classes(batch_size=batch_size, x=x_val)
print(r)
78行目まではデータの読み込みと整形部分です。80行目からがモデルを作ったり学習したりする部分です。 とても短いですね

さて、実行すると

Train on 300 samples, validate on 300 samples
Epoch 1/5000
300/300 [==============================] - 1s - loss: 5.6607 - acc: 0.4613 - val_loss: 5.5990 - val_acc: 0.8738
Epoch 2/5000
300/300 [==============================] - 1s - loss: 5.5510 - acc: 0.9046 - val_loss: 5.4750 - val_acc: 0.9215
Epoch 3/5000
300/300 [==============================] - 1s - loss: 5.4081 - acc: 0.9231 - val_loss: 5.3032 - val_acc: 0.9231
Epoch 4/5000
300/300 [==============================] - 1s - loss: 5.2126 - acc: 0.9231 - val_loss: 5.0716 - val_acc: 0.9231
Epoch 5/5000
300/300 [==============================] - 1s - loss: 4.9520 - acc: 0.9231 - val_loss: 4.7682 - val_acc: 0.9231

...

Epoch 1009/5000
300/300 [==============================] - 0s - loss: 0.2684 - acc: 0.9367 - val_loss: 0.3089 - val_acc: 0.9241
Epoch 1010/5000
300/300 [==============================] - 1s - loss: 0.2687 - acc: 0.9297 - val_loss: 0.2368 - val_acc: 0.9646
Epoch 1011/5000
300/300 [==============================] - 1s - loss: 0.2416 - acc: 0.9479 - val_loss: 0.2385 - val_acc: 0.9559

...

Epoch 1499/5000
300/300 [==============================] - 1s - loss: 0.2082 - acc: 0.9518 - val_loss: 0.2559 - val_acc: 0.9285
Epoch 1500/5000
300/300 [==============================] - 1s - loss: 0.1796 - acc: 0.9649 - val_loss: 0.1584 - val_acc: 0.9869
Epoch 1501/5000
300/300 [==============================] - 1s - loss: 0.1629 - acc: 0.9759 - val_loss: 0.1578 - val_acc: 0.9874

...

Epoch 1999/5000
300/300 [==============================] - 1s - loss: 0.0943 - acc: 0.9967 - val_loss: 0.0892 - val_acc: 0.9990
Epoch 2000/5000
300/300 [==============================] - 1s - loss: 0.0941 - acc: 0.9967 - val_loss: 0.0890 - val_acc: 0.9987
Epoch 2001/5000
300/300 [==============================] - 1s - loss: 0.1502 - acc: 0.9644 - val_loss: 0.1626 - val_acc: 0.9487

...

Epoch 2999/5000
300/300 [==============================] - 1s - loss: 0.0155 - acc: 0.9995 - val_loss: 0.0123 - val_acc: 1.0000
Epoch 3000/5000
300/300 [==============================] - 1s - loss: 0.0134 - acc: 1.0000 - val_loss: 0.0121 - val_acc: 1.0000
Epoch 3001/5000
300/300 [==============================] - 1s - loss: 0.0134 - acc: 1.0000 - val_loss: 0.0120 - val_acc: 1.0000

...

Epoch 4998/5000
300/300 [==============================] - 1s - loss: 0.0800 - acc: 0.9813 - val_loss: 6.6157e-04 - val_acc: 1.0000
Epoch 4999/5000
300/300 [==============================] - 1s - loss: 6.1272e-04 - acc: 1.0000 - val_loss: 5.0139e-04 - val_acc: 1.0000
Epoch 5000/5000
300/300 [==============================] - 1s - loss: 4.9502e-04 - acc: 1.0000 - val_loss: 4.4888e-04 - val_acc: 1.0000
というようなログが出力されます。汎化性能は無視なので、ひたすらlossを小さくしました。やりすぎですね。

99行目のprint文の出力は

[[  0   0   0   0   0   0   0   0   0   0   0   0   1]
 [  0   0   0   0   0   0   0   0   0   0   0   0   2]
 [  0   0   0   0   0   0   0   0   0   0   0   0   3]
 [  0   0   0   0   0   0   0   0   0   0   0   0   4]
 [  0   0   0   0   0   0   0   0   0   0   0   0   5]
 [  0   0   0   0   0   0   0   0   0   0   0   0   6]
 [  0   0   0   0   0   0   0   0   0   0   0   0   7]
 [  0   0   0   0   0   0   0   0   0   0   0   0   8]
 [  0   0   0   0   0   0   0   0   0   0   0   0   9]
 [  0   0   0   0   0   0   0   0   0   0   0   0  10]

...

 [  0   0   0   0   0   0   0   0   0   0   0   0 298]
 [  0   0   0   0   0   0   0   0   0   0   0   0 299]
 [  0   0   0   0   0   0   0   0   0   0   0   0 300]]
となります。きちんと、途中は全てブランク(=0)が出ていて、
単語の番号が最後に正しく出力されているのが分かります。

というわけで、LSTMは結構過去のことを覚えておいてくれることが分かりました。
そして、とても重たい連想配列ができました。

hg view

いつのまにかMercurialに面白いエクステンションが追加されていました。

hg view

Tck/Tkを使って、履歴表示やファイルの内容の表示を行ってくれます。
TortoiseHGを入れるのが面倒なときに活躍するかも。

.hgrc に

[extensions]
hgk =

を書くだけ。

確認したhgのバージョンは3.1.2です。

ソースコードを色つきHTMLに変換する方法

いつも忘れるので、メモ。

SyntaxHighlighterのようなJavaScriptを使ってソースコードの色づけをする方法もありますが、 NoScriptを使ってスクリプトをブロックする環境で見ている人にとっては許可操作が面倒なので、 HTMLで埋め込むほうが表示が簡単です。

ツールは他にもありますが、ここでは、pygmentize を紹介します。
http://pygments.org/

インストールしてしまえば、あとは使うだけ。

次のようなシェルスクリプトを作って、引数に変換したいソースコードを指定すると、HTMLファイルが作成されます。

1
2
#!/bin/bash
pygmentize -f html -O full -O style=vs,linenos=1 -o $1".html" $1
作成されたHTMLファイルの中身を記事に貼り付ければ完成です。
背景色や境界線を変更したい場合は、行番号とソースコード本体を囲っている<pre>タグにCSSのクラスを指定します。

2017/02/12

MCMC法

Markov Chain Monte Carlo (MCMC) を何のために使うのかが分かる講義(※1)。
https://www.youtube.com/watch?v=-H28H1unn0M

MCMC法をググっても手法ばかりが書かれていて、その目的の説明がすっぽり
抜け落ちているものだらけなので、MCMCって何という人は聞く(見る?)価値あり。

ものすごく短くまとめると、

  • 高次元の確率分布をランダムサンプリングで求める方法がMCMC法
  • そのうちの1つがGibbs Samplingで、条件付き確率分布から ランダムサンプリングできるのであれば、同時確率分布から ランダムサンプリングできるという手法
となる。


もう少しGibbs Samplingについて詳細に書くと、

確率変数の集合をAとして、|A|次元の同時確率分布からランダムサンプリングしたい場合は、
全てのa∈Aに対して、A\{a}の確率変数の値を決めたときに、aの確率分布から
ランダムサンプリングできるのであれば、Gibbs Samplingを使うことができる、

ということ。


(※1) http://qiita.com/shogiai/items/bab2b915df2b8dd6f6f2 からたどり着いた。

2017/02/07

Meta-learning

機械学習におけるメタ学習(Meta-learning)の2015年のレビュー論文を見つけたので、メモ。
Christiane Lemke, Marcin Budka and Bogdan Gabrys, "Metalearning: a survey of trends and technologies," Artf Intell Rev, 44, pp. 117-130, 2015
http://link.springer.com/article/10.1007%2Fs10462-013-9406-y

2016/12/12

ガンマ振動が脳内のアミロイドβを抑制する

弱っているガンマ振動の活動を戻すと、脳内のアミロイドβの蓄積を
抑制できることが報告された([1], 解説が[2,3])。

実験には、ガンマ振動の活動が弱い、アルツハイマー病のマウスモデル(5XFAD)を
使ったとのこと。これを遺伝子操作して光でガンマ振動を発生させられるようにした。
そのマウスにガンマ振動を発生させるとアミロイドβの蓄積が抑制されることが分かった。

ガンマ振動そのものがアミロイドβを消すわけではなく、
ガンマ振動によってミクログリア(microglia)がアミロイドβが蓄積している場所に集まり、
アミロイドβを食べてしまうとのこと。

ガンマ振動を非侵襲で発生させる方法[4]もあり、光で視覚野にガンマ振動を
発生させることができる。この方法を使ってガンマ振動を発生させたところ、
視覚野のアミロイドβの蓄積も抑制できたとのこと。


[1] doi: 10.1038/nature20587
[2] http://www.natureasia.com/ja-jp/nature/highlights/81380
[3] doi: 10.1038/540207a
[4] Fries, P., Nikolic D. & Singer, W. Trends Neurosci. 30, 309-316 (2007)

2016/11/12

WiMAXとWiMAX2+のレイテンシ比較

WiMAXの回線と、WiMAX2+の回線についてpingによるレイテンシの比較を行ってみた。

測定には、Windowsのpingを用い、
  ping -l 64 -n 20 8.8.8.8
を繰り返し実行することで測定した。


WiMAXのプロバイダはauで、
tracertによる経路は
  [測定PC] → [UQ Communications] → [KDDI] → [xxx.bb.kddi.ne.jp] → [google]
である。グローバルIPが割り当てられている。8.8.8.8も含め、間は10段。

WiMAX2+のプロバイダはSo-netで、
tracertによる経路は
  [測定PC] → [ICMPを拒絶する何か] → [どこかのプライベートネット(172.25.x.x)] → [xxx.bb.kddi.ne.jp] → [google]
である。どこかのプライベートネットはSo-net内部のネットワークと思われる。
8.8.8.8も含め、間は13段。


測定結果を見ると、WiMAXのレイテンシの最頻値は、80ms〜90ms。
WiMAX2+のレイテンシの最頻値は、60ms〜70ms。
ただし、WiMAXは横向きの等間隔の縞模様が現れており、 80ms〜90msの倍数のレイテンシが測定される頻度が多いことが分かる。
どちらも上限1000msで切っているが、1000msを超えるレイテンシが
観測されることもある。

なお、測定中もふつうにネットワークを利用しているので、
何かをダウンロードしている間のレイテンシは大きめに測定されている
可能性がある。

ちなみに、図はgnuplotで作成した。

2016/10/29

systemd-nspawn を使ってみる

Debian 8.5 (Jessie)環境でコンテナを作り、
テスト版であるDebian 9 (stretch)を入れてみる[1]。

Dockerは使わず、systemd-nspawnで行う。
これならJessieのパッケージ管理システムに異物を入れずに済む。

以下、作業ログ。

  1. debootstrapをインストール(aptitudeかapt-getで)。
  2. $ mkdir ~/container
  3. $ cd container
  4. ルート権限で
    $ debootstrap stretch ./stretch http://ftp.jp.debian.org/debian
    を実行。内部でchrootを使っているらしく、ルート権限がないと動作しない。

    なお、fakerootとfakechrootを使えば、ルート権限なしでも実行できるようだ[2]。
    しかし、試してみたところ、
    $ fakechroot
    $ fakeroot
    $ export PATH=/usr/sbin:/sbin:$PATH
    $ debootstrap --variant=fakechroot stretch ./stretch http://ftp.jp.debian.org/debian
    
    では残念ながら動作しなかった。詳しくは下のほうで。
  5. 作った環境のルートのパスワードを設定する。これもルート権限が必要。
    $ systemd-nspawn --directory=stretch passwd
    
    パスワード入力前に以下のメッセージが出るが、とりあえず無視。
    /etc/localtime is not a symlink, not updating container timezone.
  6. コンテナを起動する。ネットワークが必要なければ、
    $ systemd-nspawn --directory=stretch --boot
    
    を実行すればよい。

    実行すると以下のようなログが流れる。仮想マシンで起動しているかのようだ!
    Spawning container stretch on /home/xxx/container/stretch.
    Press ^] three times within 1s to kill container.
    /etc/localtime is not a symlink, not updating container timezone.
    systemd 231 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ -LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN)
    Detected virtualization systemd-nspawn.
    Detected architecture x86-64.
    
    Welcome to Debian GNU/Linux stretch/sid!
    
    Set hostname to <debian64>.
    [  OK  ] Listening on Syslog Socket.
    [  OK  ] Reached target Swap.
    [  OK  ] Listening on Journal Socket.
    [  OK  ] Listening on Journal Socket (/dev/log).
    [  OK  ] Started Dispatch Password Requests to Console Directory Watch.
    [  OK  ] Created slice System Slice.
    Mounting POSIX Message Queue File System...
    Starting Remount Root and Kernel File Systems...
    Starting Journal Service...
    [  OK  ] Created slice system-getty.slice.
    Mounting FUSE Control File System...
    [  OK  ] Reached target Encrypted Volumes.
    [  OK  ] Listening on /dev/initctl Compatibility Named Pipe.
    [  OK  ] Reached target Sockets.
    [  OK  ] Reached target Remote File Systems.
    [  OK  ] Started Forward Password Requests to Wall Directory Watch.
    [  OK  ] Reached target Paths.
    Mounting Huge Pages File System...
    [  OK  ] Reached target Slices.
    [  OK  ] Started Remount Root and Kernel File Systems.
    [  OK  ] Reached target Local File Systems (Pre).
    [  OK  ] Reached target Local File Systems.
    Starting Raise network interfaces...
    Starting Load/Save Random Seed...
    [  OK  ] Mounted POSIX Message Queue File System.
    [  OK  ] Mounted FUSE Control File System.
    [  OK  ] Mounted Huge Pages File System.
    [  OK  ] Started Journal Service.
    Starting Flush Journal to Persistent Storage...
    [  OK  ] Started Load/Save Random Seed.
    [  OK  ] Started Flush Journal to Persistent Storage.
    Starting Create Volatile Files and Directories...
    [  OK  ] Started Create Volatile Files and Directories.
    Starting Update UTMP about System Boot/Shutdown...
    [  OK  ] Reached target System Time Synchronized.
    [  OK  ] Started Update UTMP about System Boot/Shutdown.
    [  OK  ] Reached target System Initialization.
    [  OK  ] Reached target Basic System.
    [  OK  ] Started Regular background program processing daemon.
    Starting System Logging Service...
    [  OK  ] Started Daily apt activities.
    [  OK  ] Started Daily Cleanup of Temporary Directories.
    [  OK  ] Reached target Timers.
    [  OK  ] Started Raise network interfaces.
    [  OK  ] Reached target Network.
    Starting Permit User Sessions...
    [  OK  ] Started Permit User Sessions.
    [  OK  ] Started Console Getty.
    [  OK  ] Reached target Login Prompts.
    [  OK  ] Started System Logging Service.
    [  OK  ] Reached target Multi-User System.
    [  OK  ] Reached target Graphical Interface.
    Starting Update UTMP about System Runlevel Changes...
    [  OK  ] Started Update UTMP about System Runlevel Changes.
    
    Debian GNU/Linux stretch/sid debian64 console
    
    debian64 login: root  
    Password: ←ここで先ほど設定したパスワードを入力
    Linux debian64 3.16.0-4-amd64 #1 SMP Debian 3.16.36-1+deb8u1 (2016-09-03) x86_64
    
    The programs included with the Debian GNU/Linux system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.
    
    Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
    permitted by applicable law.
    
  7. 終了するときは、ログの最初に書かれているように、
    Press ^] three times
    とすればよい。つまり Ctrl+] を3回連続で押す。
さて、以降の内容は、通常ユーザー権限によるコンテナ起動に関するメモである。
結論から言うと、通常ユーザーでコンテナは起動できず、ルート権限が必要である。

こんなことを試したのは、単に起動するたびにルート権限がいるのは面倒だというだけのこと。
まず、
$ fakeroot
$ systemd-nspawn --directory=stretch --boot
を実行してみたが、
Press ^] three times within 1s to kill container.
clone() failed: Operation not permitted
が表示されて起動できない。

というわけで、4.からfakechrootとfakerootを使った環境で作業をしてみる。
$ fakechroot
$ fakeroot
$ export PATH=/usr/sbin:/sbin:$PATH
$ debootstrap --variant=fakechroot stretch ./fake-stretch http://ftp.jp.debian.org/debian

最後に次のメッセージを出力して止まってしまった。
W: Failure trying to run: chroot /home/xxx/container/fake-stretch dpkg --force-depends --install /var/cache/apt/archives/base-passwd_3.5.40_amd64.deb
W: See /home/xxx/container/fake-stretch/debootstrap/debootstrap.log for details (possibly the package matches is at fault)
debootstrap.logには、
Setting up base-passwd (3.5.40) ...
Can't load '/usr/lib/x86_64-linux-gnu/perl-base/auto/POSIX/POSIX.so' for module POSIX: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.23' not found (required by /home/xxx/container/fake-stretch/usr/lib/x86_64-linux-gnu/perl-base/auto/POSIX/POSIX.so) at /usr/lib/x86_64-linux-gnu/perl-base/XSLoader.pm line 96.
^@ at /usr/lib/x86_64-linux-gnu/perl-base/POSIX.pm line 36.
Compilation failed in require at /usr/share/perl5/Debconf/Template.pm line 7.
BEGIN failed--compilation aborted at /usr/share/perl5/Debconf/Template.pm line 7.
Compilation failed in require at /usr/share/perl5/Debconf/Question.pm line 8.
BEGIN failed--compilation aborted at /usr/share/perl5/Debconf/Question.pm line 8.
Compilation failed in require at /usr/share/perl5/Debconf/Config.pm line 7.
BEGIN failed--compilation aborted at /usr/share/perl5/Debconf/Config.pm line 7.
Compilation failed in require at /usr/share/perl5/Debconf/Log.pm line 10.
Compilation failed in require at /usr/share/perl5/Debconf/Db.pm line 7.
BEGIN failed--compilation aborted at /usr/share/perl5/Debconf/Db.pm line 7.
Compilation failed in require at /usr/share/debconf/frontend line 6.
BEGIN failed--compilation aborted at /usr/share/debconf/frontend line 6.
dpkg: error processing package base-passwd (--install):
subprocess installed post-installation script returned error exit status 255
Errors were encountered while processing:
base-passwd
が出力されていた。

無視して、そのまま(fakeroot環境のまま)
systemd-nspawn --directory=stretch --boot
を実行してみたが、
Press ^] three times within 1s to kill container.
clone() failed: Operation not permitted
と表示され、やはり実行できない。

コマンドの実行方法を替えてみる[3]。
$ fakechroot fakeroot debootstrap stretch ./fake-stretch http://ftp.jp.debian.org/debian
しかし、同じエラーで停止する。

そこで、debootstrapの代わりにcdebootstrapを使ってみる。
$ fakechroot fakeroot cdebootstrap stretch ./fake-stretch http://ftp.jp.debian.org/debian 

stretchがないといわれる。testingに替えても、結局、
E: Unknown suite stretch
といわれる。

[4]によると、/usr/share/cdebootstrap/suitesを修正すればよいらしい。
Suite: stretch
Config: generic
Keyring: debian-archive-keyring.gpg
を追加した。これでstretchのパッケージの取得は動作するようになったが、結局、
...
P: Validating wget
P: Retrieving whiptail
P: Validating whiptail
E: Failed to unshare: Operation not permitted
が表示されて、停止。

仕方がないので、ルート権限で
$ cp -r stretch stretch2
$ chown -R xxx stretch2
として、ルート権限で作ったstretchのディレクトリを一般ユーザーxxxに変更して、
$ fakeroot systemd-nspawn --directory=stretch2 --boot
を試してみたが、だめ。
$ fakechroot fakeroot systemd-nspawn --directory=stretch2 --boot
もだめ。
clone() failed: Operation not permitted
というエラーが出る。

[5]によると、cloneには、CAP_SYS_ADMINが必要らしい。
さらに[6]によると、
# Linux 3.8 以降では、ユーザー名前空間の作成にどのケーパビリティも必要としない
ということなので、カーネルのバージョンを見てみると、
$ uname -r
3.16.0-4-amd64
であった。あれ? 3.8以上のように見えるが…。

とりあえず、systemd-nspawnにCAP_SYS_ADMINを付与してみる。
$ setcap "CAP_SYS_ADMIN=eip" /usr/bin/systemd-nspawn

設定されたか確認してみると、
$ getcap /usr/bin/systemd-nspawn
/usr/bin/systemd-nspawn = cap_sys_admin+eip
となっており、設定された。

この状態で一般ユーザーで実行してみる。

$ fakeroot systemd-nspawn --directory=stretch2 --boot
ERROR: ld.so: object 'libfakeroot-sysv.so' from LD_PRELOAD cannot be preloaded (cannot     open shared object file): ignored.
Need to be root.
となり、残念ながら起動できない。

fakeroot-ng [7]というパッケージがあるので、それをfakerootの代わりに使ってみた。
しかし、CAP_SYS_ADMINの設定の有無に関わらず、

$ fakeroot-ng systemd-nspawn --directory=stretch2 --boot
Spawning container stretch on /home/xxx/container/stretch.
Press ^] three times within 1s to kill container.
clone() failed: Operation not permitted
となり、やはり起動できない。

CAP_SYS_ADMINはあきらめ、[8]を参考に、

$ echo 1 > /proc/sys/kernel/unprivileged_userns_clone
を設定してみたが、
$ systemd-nspawn --directory=stretch2 --boot
$ fakeroot systemd-nspawn --directory=stretch2 --boot
$ fakeroot-ng systemd-nspawn --directory=stretch2 --boot
の3つとも動作しない。

やはりルート権限が必要なようだ。


--- 参考 ---
[1] https://lindenberg.io/blog/post/debian-containers-with-systemd-nspawn/
[2] http://rail.hatenablog.jp/entry/2012/10/25/234638
[3] https://github.com/dex4er/fakechroot/wiki
[4] http://d.hatena.ne.jp/embedded/20150926/p1
[5] http://systemd-devel.freedesktop.narkive.com/GchaIOxs/systemd-nspawn-create-container-under-unprivileged-user
[6] https://linuxjm.osdn.jp/html/LDP_man-pages/man7/capabilities.7.html
[7] https://fakeroot-ng.lingnu.com/index.php/Home_Page
[8] http://systemd-devel.freedesktop.narkive.com/GchaIOxs/systemd-nspawn-create-container-under-unprivileged-user

2016/07/10

uniq でフィールド指定をする方法

uniqで特定のフィールドに対して重複するものを除去するには、
オプション -f と -w を使えばよい。

フィールドは、スペースかタブで区切られた文字列。
-wは比較に使用するフィールドのバイト数。
-fが行頭から順に取り除くフィールドの数。

取り除いたフィールドとその次のフィールドとの間のスペースまたはタブは取り除かれない
そのため、-wと組み合わせるときは、-wのバイト数の計算に注意が必要である。

[a.txt]
11 abc ABC
12 abc xXx
234  abb  XXX
456 bbb XXX
234 bbbc YYY

2個目のフィールドの重複を取り除くには、
$ uniq -f1 -w5 a.txt
11 abc ABC
234  abb  XXX
456 bbb XXX
234 bbbc YYY
とする。-w5は、a.txtの5行目の2番目のフィールドとその直前のセパレータを合わせたバイト数が5であるため。

uniqではフィールド幅が可変長のフィールドの重複除去は行いにくい。
仮に-w6にすると、
$ uniq -f1 -w6 a.txt
11 abc ABC
12 abc xXx
234  abb  XXX
456 bbb XXX
234 bbbc YYY
のようになる。これは、比較に使用される文字が
 abc A
 abc x
  abb 
 bbb X
 bbb c
となるため(行頭にスペースが1個入っていることに注意)。

したがって、
[b.txt]
11 abc ABC
12 abc DEF
13 abcd xXx
14 abcde XXX
15 abcde YYY
に対して、2番目のフィールドで重複除去をしようとしても、
$ uniq -f1 -w6 b.txt
11 abc ABC
12 abc DEF
13 abcd xXx
14 abcde XXX
のようになってしまう。これを避けるには、対象とするフィールドを
行末にコピーしてuniqを行ったあと、それを除去すればよい。
例えば、
$ awk '{print $0" "$2}' b.txt | uniq -f3 | sed 's/ [[:graph:]]*$//'
11 abc ABC
13 abcd xXx
14 abcde XXX
のようにする。

日本語も処理できるが、-wがバイト数のため指定しにくい。
[c.txt (UTF-8)]
11 abc ABC
12 abc DEF
13 あさ xXx
14 あさ XXX
15 あみ YYY

例えば、2番目のフィールドの幅を4バイトとすると、
$ uniq -f1 -w4 c.txt
11 abc ABC
13 あさ xXx
となり、「あ」しか比較対象にならない。
なので、
$ awk '{print $0" "$2}' c.txt | uniq -f3 | sed 's/ [[:graph:]]*$//'
11 abc ABC
13 あさ xXx
15 あみ YYY
のように、後ろに移動させてからuniqするのが簡単。

2016/05/28

フォントの高さの修復方法

TeXで使われているフォントをWindowsで使おうとすると、
行の高さ(line-space)が壊れていることがあるので修復してみた。

TeXで使われているフォントは、
https://www.ctan.org/
で入手できるものが多い。

TeXでデフォルトで使われるフォントはComputer Modern Unicode
https://www.ctan.org/pkg/cm-unicode
で、このフォントの高さは正常なのでそのままWindowsのフォントとして使用できる。

しかし、すべての文字についてデフォルトがComputer Modernだというわけではない。
数式で使用されるギリシャ文字にはLatin Modern Math (LM Math)
http://www.gust.org.pl/projects/e-foundry/lm-math
https://www.ctan.org/tex-archive/fonts/lm-math
が用いられている。

残念ながら、この記事を書く時点では、このLatin Modern Mathの
フォントファイルに記録されている行の高さ情報は正しくなく、
上下におよそ3行分ずつ余分な空白が入ってしまう。
WordやLibreOfficeで使おうとするととても使いにくい。


そこで、FontForge
https://fontforge.github.io/en-US/
を使ってフォントの高さの修復を行う。

なお、FontForgeで検索すると日本語で書かれている
http://fontforge.github.io/ja/
がヒットするが、そこからたどりつく
https://sourceforge.net/projects/fontforge/files/fontforge-executables/
にある
FontForgeSetup_2012-07-31_Windows.exe
は使わないほうがよい。古く、日本語が正常に表示されない。

一方、Cygwin版
fontforge_cygwin-20090914.tar.bz2
なら表示が崩れることはない。しかし、fontforgeのGUI表示用にCygwinのXが必要になるので
CygwinのXをインストールしなければならず、また、FontForge自体が古い。


さて、FontForgeの準備ができたら、起動して修正したいフォントを読み込み、
次の手順にしたがってフォントを修正する。

  1. エレメント→フォント情報と進み、設定画面を表示させる。Cygwin版ならElement→Font Infoと進む。 
  2. 左側の一覧から「一般情報(General)」を選択する。 
  3. 高さ(Ascent)と深さ(Descent)の値を覚える。
  4. 左側の一覧から「OS/2」を選択し、「メトリック(Metrics)」タブを選択する。
  5. 「オフセットを指定(Is Offset)」のチェックボックスをすべてはずす。
  6. Win AscentとWin Descentの値をそれぞれ3.で覚えた値に設定する。
  7. 組版上の高さ(Typo Ascent)とhheaテーブルでの高さ(HHead Ascent)を3.で覚えた高さの値に設定する。
  8. 組版上の深さ(Typo Descent)とhheaテーブルでの深さ(HHead Descent)を3.で覚えた深さの値を負にした値に設定する。例えば、深さが194であれば-194を設定する。
  9. OKボタンを押す。
  10. ファイル(File)→フォントを出力(Generate Fonts)と進む。
  11. 出力形式を指定する。今、修正しようとしているLatin Modern MathはOpenTypeフォントなので、OpenTypeを選択する。
  12. 生成ボタンを押す(フォントによっては途中で警告表示がでるかもしれない)。
  13. 生成したフォントファイルのプレビュー(エクスプローラで右クリック→プレビュー)を表示すると、無駄な行間スペースがなくなっていることが確認できる(はず)。

以上で行の高さを修正したフォントが得られるので後はシステムにインストールするだけである。

フォントの高さの設定に関しては、以下を参考にした。
http://fontforge.github.io/faq.html#linespace
https://fontforge.github.io/ja/fontinfo.html#TTF-Metrics
http://designwithfontforge.com/en-US/Line_Spacing.html