GnukのEmulation mode

2021/3/10 FSIJセミナーで解説されたのでメモ

これでできること:

  • 実物のGnukを使わない動作テスト
    • 鍵生成などは失敗すると最悪なにもできなくなるので練習ができる

事前に必要なもの

  • scdaemon
  • usbip
  • python3-cffi,
  • python3-pytest

ソースコードの準備とビルド手順、起動まで

別のターミナルを開き、vhci, usbipを用いてUSBデバイスとして扱えるようにする

$ git clone -b STABLE-BRANCH-1-2 git://git.gniibe.org/gnuk/gnuk.git
$ cd gnuk && git submodule update --init
$ cd src
$ ./configure --target=GNU_LINUX --enable-factory-reset
$ make
$ cd build
$ ./gnuk --vidpid=234b:0000
Gnuk (emulation with USBIP), a GnuPG USB Token implementation
Copyright (C) 2021 Free Software Initiative of Japan
This is free software under GPLv3+.
USBIP thread started.
You can use this by attaching following commands:
    # modprobe vhci_hcd
    # usbip attach -r 127.0.0.1 -b 1-1
User interaction thread for AckBtn started.
$ sudo modprobe vhci_hcd
$ sudo usbip attach -r 127.0.0.1 -b 1-1
$ lsusb | grep 234b
Bus 005 Device 002: ID 234b:0000
$ lsusb -t
(略)
/:  Bus 05.Port 1: Dev 1, Class=root_hub, Driver=vhci_hcd/15p, 480M
    |__ Port 1: Dev 2, If 0, Class=Chip/SmartCard, Driver=, 12M
(略)

テストの動作

$ cd ../../tests
$ pytest3 -x
============================= test session starts ==============================
platform linux -- Python 3.7.3, pytest-3.10.1, py-1.7.0, pluggy-0.8.0
rootdir: /home/knok/gnuk/tests, inifile:
collected 416 items

test_000_empty_card.py ..............................                    [  7%]
(略)
test_025_kdf_none.py ...                                                 [100%]

========================= 416 passed in 12.17 seconds ==========================

gpgでカードとして扱う

PINはadmin: 12345678, 通常 123456

  • 初期化 (factory-reset)
  • 新規鍵生成 (generate)
    • 内容は ~/.gnuk-flash-image に保存される
  • 作成した鍵で暗号化 (gpg -e)
$ gpg --edit-card

Reader ...........: 234B:0000:FSIJ-1.2.17-EMULATED:0
Application ID ...: D276000124010200FFFEF1420A7A0000
Version ..........: 2.0
Manufacturer .....: unmanaged S/N range
Serial number ....: F1420A7A
Name of cardholder: [未設定]
Language prefs ...: [未設定]
Sex ..............: 無指定
URL of public key : [未設定]
Login data .......: [未設定]
Signature PIN ....: 強制
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 0
KDF setting ......: off
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

gpg/card> admin
gpg/card> factory-reset
gpg: OpenPGPカードno. D276000124010200FFFEF1420A7A0000を検出

gpg: 注意: このコマンドはカードに保管してあるすべての鍵を破壊します!

続けますか? (y/N) y
工場出荷リセットを行いますか? (本当なら "yes" と入力) yes

gpg/card> key-attr
こちらのカード鍵の属性を変更します: 署名鍵
ご希望の鍵の種類を選択してください:
   (1) RSA
   (2) ECC
あなたの選択は? 2
ご希望の楕円曲線を選択してください:
   (1) Curve 25519
   (4) NIST P-384
あなたの選択は? 1
カードは、今、こちらのタイプの鍵を生成するように再コンフィグされます: ed25519
注意: カードが要求された鍵長をサポートしているという保証はありません。
      鍵生成が成功しない場合、あなたのカードに関する技術文書を確認し、
      利用できる鍵長について確認ください。
こちらのカード鍵の属性を変更します: 暗号化鍵
ご希望の鍵の種類を選択してください:
   (1) RSA
   (2) ECC
あなたの選択は? 2
ご希望の楕円曲線を選択してください:
   (1) Curve 25519
   (4) NIST P-384
あなたの選択は? 1
カードは、今、こちらのタイプの鍵を生成するように再コンフィグされます: cv25519
こちらのカード鍵の属性を変更します: 認証鍵
ご希望の鍵の種類を選択してください:
   (1) RSA
   (2) ECC
あなたの選択は? 2
ご希望の楕円曲線を選択してください:
   (1) Curve 25519
   (4) NIST P-384
あなたの選択は? 1
カードは、今、こちらのタイプの鍵を生成するように再コンフィグされます: ed25519

gpg/card> generate
暗号化鍵のカード外バックアップを作成しますか? (Y/n) n

工場設定のPINは下記のとおり
   PIN = '123456'     管理者PIN = '12345678'
次のコマンドを使って変更すべきです --change-pin

鍵の有効期限を指定してください。
         0 = 鍵は無期限
      <n>  = 鍵は n 日間で期限切れ
      <n>w = 鍵は n 週間で期限切れ
      <n>m = 鍵は n か月間で期限切れ
      <n>y = 鍵は n 年間で期限切れ
鍵の有効期間は? (0)
鍵は無期限です
これで正しいですか? (y/N) y

GnuPGはあなたの鍵を識別するためにユーザIDを構成する必要があります。

本名: Test User
電子メール・アドレス: test@example.org
コメント:
次のユーザIDを選択しました:
    "Test User <test@example.org>"

名前(N)、コメント(C)、電子メール(E)の変更、またはOK(O)か終了(Q)? o
gpg: 鍵B805A9C910609E90を究極的に信用するよう記録しました
gpg: 失効証明書を '/home/knok/.gnupg/openpgp-revocs.d/70486A8D5034085FCFE80844B805A9C910609E90.rev' に保管しました。
公開鍵と秘密鍵を作成し、署名しました。

gpg/card> quit
pub   ed25519 2021-03-10 [SC]
      70486A8D5034085FCFE80844B805A9C910609E90
uid                      Test User <test@example.org>
sub   ed25519 2021-03-10 [A]
sub   cv25519 2021-03-10 [E]

$ gpg -o /tmp/encrypt.gpg -r test -e /etc/motd
gpg: 信用データベースの検査
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: 深さ: 0  有効性:   2  署名:   0  信用: 0-, 0q, 0n, 0m, 0f, 2u
gpg: 次回の信用データベース検査は、2021-10-06です

追記(2020-03-12) Facebookでg新部さんよりいくつかご指摘いただきました

  • encryptでは秘密鍵を使わない(のでここはテストになっていない)
  • Debian 10 (Buster)のGnuPG 2.2.12では復号化に失敗する。2.2.27では問題ないとのこと
  • Gnuk 1.2.17時点で鍵生成にバグがある。今は修正がpushされている

Nuro光のルーター(HG8045Q)でDMZからglobal側のIPアドレスを取得する(UPnP)

最近、自宅の回線をNuro光にしました。今までLinuxマシンにPPPoEルーターをやらせていたのですが、それと同等の環境を得るためにDMZへLinuxマシンを置いて処理をやらせるようにしました。

ここで一つ問題なのが、いかにして外部サービスに頼らすglobal IPのアドレスを調べるかです。ルーターはISPから貸与されたHG8045Qであり、管理画面は今どきのJavaScript必須なwebインターフェースです。httpで管理画面をたたいてアドレスを取得するのは面倒そうでした。できれば外部サービスには頼りたくありません。

UPnPのNAT越えについて調べてみた – いろいろな何か :こちらの記事を参考に、UPnPでアドレスを取得する方法を行いました。

まずは記事に習ってM-SEARCHを投げます。Python3用コードに書き直しました。

#!/usr/bin/python3

import socket

M_SEARCH = 'M-SEARCH * HTTP/1.1\r\n'
M_SEARCH += 'MX: 3\r\n'
M_SEARCH += 'HOST: 239.255.255.250:1900\r\n'
M_SEARCH += 'MAN: "ssdp:discover"\r\n'
M_SEARCH += 'ST: upnp:rootdevice\r\n'
M_SEARCH += '\r\n'
M_SEARCH = bytes(M_SEARCH, 'ascii')
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.settimeout(5) # 5秒でタイムアウト
s.bind(('', 1900))
# M-SEARCHをマルチキャストする
s.sendto(M_SEARCH, ('239.255.255.250', 1900))
while True:
try:
response, address = s.recvfrom(8192)
print('from', address)
       print(response.decode('ascii'))
print('=' * 40)
except socket.timeout as e: # タイムアウトしたときの処理
print(e)
break
s.close()

これを実行するとDLNAサーバになれるWindows10マシンたちも反応するのですが、必要なルータ(手で固定IPとして設定した)のレスポンスのみに着目します。

HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Thu, 14 May 2020 22:12:07 GMT
EXT:
LOCATION: http://192.168.xx.254:49652/49652gatedesc.xml
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 0405f658-xxxx-xxxx-xxxx-e6e487df1fbe
SERVER: Linux/3.10.53-HULK2, UPnP/1.0, Portable SDK for UPnP devices/1.6.18
X-User-Agent: UPnP/1.0 DLNADOC/1.50
ST: urn:schemas-upnp-org:service:Layer3Forwarding:1
USN: uuid:00e0fc37-xxxx-xxxx-xxxx-084F0AC2AED2::urn:schemas-upnp-org:service:Layer3Forwarding:1


LOCATIONに書かれているURLにアクセスしてみます。

 <?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<device>
<deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType>
<friendlyName>Huawei IGD</friendlyName>
<manufacturer>Huawei</manufacturer>
<manufacturerURL>www.huawei.com</manufacturerURL>
<modelDescription>Huawei EchoLife Series</modelDescription>
<modelName>Huawei EchoLife Series</modelName>
<modelNumber>Huawei ONT</modelNumber>
<modelURL></modelURL>
(略)
<service>
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
<controlURL>/upnp/control/WANIPConn1</controlURL>
<eventSubURL>/upnp/control/WANIPConn1</eventSubURL>
<SCPDURL>/gateconnSCPD.xml</SCPDURL>
</service>
(以下略)

参考にした記事ではWANPPPConnection:1を見るようになっていましたが、Nuro光はIPoEでアドレスをもらうためWANIPConnection:1を見るようにすれば良いようです。以下のようなコードでSOAPリクエストを投げ、結果をparseしました。

#!/usr/bin/python3
#-*- coding:utf-8 -*-

import urllib.request, urllib.error, urllib.parse
from bs4 import BeautifulSoup

HOST = '192.168.xx.254'
PORT = 49652
CONTROL = '/upnp/control/WANIPConn1'
URL = 'http://' + HOST + ':' + str(PORT) + CONTROL

SOAP = '''<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
     s:encodingStyle="http://schemas.xmlsoap.org/encoding/">
  <s:Body>
    <m:GetExternalIPAddress xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1">
    </m:GetExternalIPAddress>
  </s:Body>
</s:Envelope>
'''
SOAP = bytes(SOAP, 'ascii')

req = urllib.request.Request(URL, data=SOAP)
req.add_header('Content-Type', 'text/xml; charset="utf-8"')
req.add_header('SOAPACTION', '"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"')
#req.add_data(SOAP)

res = urllib.request.urlopen(req)

x = res.read()
x = x.decode('ascii')

soup = BeautifulSoup(x, 'xml')
ex = soup.find_all('NewExternalIPAddress')[0]
ipaddr = ex.text
print(ipaddr)

これで無事グローバスIPアドレスを外部に頼らず取得できるようになりました。これでddnsの更新が行えます。

“Adversarial Policies: Attacking Deep Reinforcement Learning”をざっと読んだ

機械学習で画像分類器をだますAdversarial Exampleは広く知られていますが、強化学習でも同じ問題があるそうです。observation対して摂動を与えることで意図しない挙動をさせることができるそうです。

observationは第三者が直接介在できるものではないのですが、環境を共有するゼロサムゲームにおいて、自身の挙動を介して相手のobservationに狙った摂動を与える、blackbox, model-freeな手法が提案されている(
https://adversarialpolicies.github.io/ )とのことでちょっと論文を読んでみました。

さすがに状況は限定されているようで、環境が高次元であること、デプロイされたエージェントの重みが固定されているという状況で、無制限に相手のポリシーがサンプリングできることが条件のようです。

見た感じ、人間同士のゲームプレイにおけるフェイントに似ているなあという印象を受けました。生成されたadversarial policyに対してfine-tuningはできるものの、それに対してさらなるadvaersarial policyを生成することができるそうで、それを交互に繰り返すことでロバストなポリシーにできるとのことです。

adversarial policyの学習は相手agentのself-playによる学習にかかる時間の3%程度でできてしまうとのことで、adversarial exampleの世界でも攻撃者有利なのは相変わらずのようです。

バーチャル背景を任意のサービスで利用する

webcamにアクセスして、人体を検出し背景画像と合成をするページ
https://knok.github.io/virtbg/ を作りました。TensorFlow.js, BodyPixを使っています。

ページそのものは単にカメラへのアクセスと画像合成だけを行うので、これをオンラインミーティングサービス等で使うためには何かしらの手段で出力をカメラに見せる必要があります。Qiitaの記事として、OBS studioとプラグインを組み焦る方法を書きました。

Windows, Linux, Macそれぞれに対応するプラグインがあるので、それらのプラットフォームで利用できます。Linuxならv4l2loopbackを使います。

ブラウザ->OBS->プラグインという処理パイプラインになるので、これでも若干のオーバーヘッドがあります。Google Meetでカメラを差し替えるChrome拡張として実装した人がいるので、この方法ならオーバーヘッドはより少なく済んでいそうです。私は面倒だったのでOBSに機種依存部分を丸投げしてしまいましたが…その代わり任意のサービスに利用ができます。Jistiでも使えるので次のオンラインセミナーではこれを使うつもりです。

Debian buster上でflatpakのSteamとProtonを動かす

前々からsnap/flatpak,  wine on dockerなど色々な方法でWindows用のゲームをDebianから動かそうと時々試していたのですが、環境をそこそこ汚さずに動作できるようになりました。

flatpak + Steam

flatpakはコンテナを利用したソフトウェア配布ツールです。同様のものにsnapがありますが、flatpakはデスクトップアプリ向けらしいです。flatpak自体はaptでインストールできます。

Steamのインストール、実行は以下のようなコマンドで行います。

flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak install com.valvesoftware.Steam
flatpak run com.valvesoftware.Steam

Protonの有効化

以下の記事を参考にしました。

Days of Speed Debian StretchにSteamをインストールしてSteam Play(Proton)でWindowsゲームを遊ぶ

flatpakで入れた場合i386ランタイム等も一緒に入れてくれるので、add-architectureなどをする必要はありません。単純にSteamの設定でベータに参加しProtonを有効化するだけです。

ゲームをインストール

一般的なSteamの操作でWindows向けゲームをインストールします。初回はProtonのダウンロードとインストールが行われます。ゲームによって必要なVC runtime等も必要な場合はゲームの初回起動時に行われます。

簡単に手元で動いたものを列挙しておきます

ファイルの実体が置かれる場所

~/.var/app/com.valvesoftware.Steam 以下に配置されます。mod等を入れるならここを直接いじることになるでしょう。flatpakでSteamを消してもここは残るので不要なら直接rmしましょう。

ToDo/課題

  • ジョイスティックの利用
  • Intel Graphics以外のGPUでの動作確認
  • さらなるゲームの動作確認

追記(2019/9/10)

flathubのリポジトリ追加コマンドを記載しました。

転職活動についての総括

 

以前退職予定エントリーを書きましたが、退職・無職・就職という過程を経てひと段落ついたので、総括をしてみようと思います。

いつから転職活動を行っていたか

実のところ、3年ぐらい前(2015年頃)から転職活動を始めていました。なかなか思うような結果が出なかったのですが、2017年度になって早期退職制度の対象者となり、良い機会だったので応募し、受理されて無事円満退職となりました。

一応、転職以外の道として単純に給与あるいはそれ以外の何かで収入を増やす手段も検討し、そういったことを当時の上長にも相談したりということは行いましたが、会社の給与体系から逸脱するようなことはできませんでした。

どのような手段を用いたか

先の退職エントリーをSNSで広める以外に、以下の手段を用いました。

  • エージェントの利用
  • 転職サイトの利用

エージェントについて

転職を考え始めた時期に、転職エージェントのほうから声がかかりました。いくつかのアドバイスを得ることができ、個人的には有用でした。特に職務経歴書の書き方は非常に参考になりました。

転職サイトの利用

転職ドラフトに登録し、何度かドラフトに参加しました。登録した理由は、たまたま見かけたPR記事が知人だったからです。

いくつかの指名を得ることはできましたが、最終的にマッチングがうまくいかなかったかなあ、という感じです。一つ残念だったのは、「提示した年俸の最低90%を指名企業は提示する」というルールが守られていなかったことです。この点については運営に直接伝えてはいます。

LinkedIn

かなり前からLinkedInのアカウントは持っていました。特に外資企業からの連絡はLinkedIn経由でそこそこありましたが、resumeを送ってその後音沙汰なし、ということも多かったです。

SNSでの退職エントリー公開について

自分が予想していたよりは大きな反響がありました。10社ぐらいはコンタクトがありました。それでもやはりマッチングには苦労し、最終的に今の会社に雇用されました。

エントリーはblogで、発信は主にTwitterで行いましたが、連絡手段としてはFacebook経由が最も多く、次いでメール、Twitterという感じでした。転職活動をするにあたってFacebookアカウントはあったほうが良いのかなと思います。

前職を辞めるにあたって

早期退職制度の申請に関しては非常にスムーズでした。上司たちも「会社が用意した正当な手段であり行使は自由だ」といって下さり、実際に円満退社できたことには感謝しています。

転職支援制度の利用

早期退職制度の一環として、転職コンサルティング会社の1年間分の提供があり、一応使ってはみました。

私を担当したコンサルタントの方はあまり自分の関心のある分野には明るくなかったようでしたが、それでもいくつか見繕ってはくれました。私の職務経歴書を見たうえで「これぐらいの年収が適切だろう」というものを提示してくれたので、客観的な市場価値を知ることができた(そしてやはり前職の給与は安かった)のはよかったと思います。

新しい職場について

最初の方では軽く触れましたが、2018年4月16日付けでロボット投信株式会社に入社し、働いております。

現状は主にR&Dを担当しており、自然言語処理を中心としています。投資、ファイナンスに関する研究発表は人工知能学会、言語処理学会でも割となされており、それらの再現実験や、機械学習の実サービスへの応用の模索といったことを行っています。

その兼ね合いもあり、6月に鹿児島で開催された人工知能学会全国大会にも業務として参加しました。FLOSS活動にも理解が深く、7月~8月に開催されたDebConf18も業務扱いで参加させてもらうことができました。

ロボット投信株式会社ではエンジニアも募集しています。Wantedlyに募集ページがありますが、私個人へのコンタクトも受け付けております。もし興味のある方がいらっしゃいましたらご一報ください。

 

2018年のエイプリルフール

自分の観測範囲で見かけたものをまとめてみます。ほかにFLOSS関連でこんなのがあった、という話があれば追加します。

Free Software Foundation announces crowdfunding campaign to buy Facebook — Free Software Foundation

FSFがFacebookを買収するためにクラウドファンディングを始めた、というネタ。ところでFacebookはGnuPGに寄付をしています。

Bug#894551: ITP: fascism — Exhaustive exploration of Fascist theory and practice

ファシズムをDebianパッケージ化する提案です。過去にもrootkitのITPなど、エイプリルフールネタはありました。

退職予定エントリ

「俺は仕事をやめるぞー!ジョジョーーッ!」

一度は書いてみたかったセリフなのですが、まだ確定ではありません。オリジナルは荒木飛呂彦作の「ジョジョの奇妙な冒険」第一部、ディオのセリフです。

経歴について

職務経歴書を公開します。

2017/12現在の状況

今年から早期退職制度 の対象者となったので、周囲に相談しつつ申請をしたという状況です。承認されれば今年度一杯をもって退職となります。

理由

一言でいえば「待遇に不満がある」のが理由です。調査会社による年齢ごとの年収調査によると、残業を含めた上での額面での現在の年収に対し、平均から-200、最低値と比較しても-100という状態です。

残業に関しては正直なところ「生活残業」状態であり、限度いっぱい働いているような状況です。2年ほど前から私生活上どうしても収入が必要になってしまったのですが、貯蓄もできずぎりぎりの状態を続けています。一方で心身の健康上今の生活を継続するのも厳しい、このままで健康を取り戻すことはできそうにない、ということで申請の決意をしました。

 これまでの経験

1990年台後半当初は名古屋で働いていました。当時はインターネットの商業展開が始まった時期で、基本的にDebianでサーバを立てたり、CGIベースでプログラムを組んだりといったことを行っていました。全文検索システムにfreeWAISやNamazuを使うこともありました。当時はFLOSS活動が業務と直結していて、この時代は業務時間内のコミットも自由にできていました。この頃はDebian Projectは2000年問題に対して積極的な取り組みをしており、私自身も2000年をむかえる時にサーバーが無事年を越せるか現場待機していました。一人で。業務で日をまたいだことは、生涯を通してこの日だけです。仕事で徹夜などをしたことはありません。

21世紀に入ったところで、東京へ転勤になりました。東京での仕事はこれまでとは大きく変わりました。当初は電子ペーパー上にレンダリングする組版システムの担当ということだったのですが、2年ほどしてまったく違うプロジェクトにかわり、それ以後半年から2年程度という短いスパンで大きく違うプロジェクトに回される、というサイクルを繰り返しています。

直近では、機械学習を主題とした課題に取り組んでいます。画像処理・自然言語処理が中心です。ここ2年ぐらいは学生の頃よりたくさん論文を読んでいます。オープンアクセス万歳。

現職の良いところ

2000年代前半まではかなり自由にやらせてもらえました。Linux協会の活動も業務の一環として行っていましたし、FLOSS活動にも特に制限はありませんでした。

会社も安定はしています。これまでに働いていて、赤字だった年度は1回だけでした。ただ、主力である印刷に関しては斜陽であり、いかに新しい事業に活路を見出すかというのは常に課題です。

私が携わった範囲では、新しい技術に触れることができていました。これは個人的に環境として恵まれていたと思っています。必ずしも全社的にそういう傾向があるわけではありません。基本的にはオールドファッションな社風です。

労働環境も世間的に見ればかなりよい方でしょう。もっとも、事業部によっては激務であるとは聞いています。その代わりに残業代はきちんと出ます。

 自己アピール

ソフトウェアについて、幅広い範囲を取り組んできました。半年から2年で内容が変わりながらもなんとか追いついてそれなりの成果を出すことができていたと自分では思っています。

一方で長年取り組んできたのはDebian ProjectをはじめとするFLOSS活動です。アクティブでない年もありましたが、なんとか今も継続してやっています。最近ではuim回りでいろいろお手伝いをしています。このブログをホスティングしているマシンも20年以上Debian hammあたりからアップグレードを重ねてきています。さすがにいい加減i386は厳しいなという状況で、amd64への移行をいずれどこかでやる必要があるなと感じつつ数年経過しています。

私の職務経歴書、このブログ、GitHub, Qiita等を見て人材として興味のある方がいれば、ぜひメール、SNS(Twitter, Facebook) 等でご連絡ください。

 

GANsやOpenCVをいじっている

ここのところ、画像処理関連をやってみています。

以前pix2pixをいじっていましたが、そこでも使われているGANs(Generative Adverarial Networks)という技術を、なにかしら自分の趣味の範囲で利用できないかなあと試してみています。

初期に提案されたDCGANは非常に衝撃的だったのですが、いかんせんハイパーパラメータのチューニングがシビアでした。学習がうまくいかないとmode collapseという現象を起こし、まったく意味不明な画像しか生成できなくなります。

この辺りの数理的理解が進んできて、mode collapseを起こしにくい新しい手法が提案されています。

chainer-gan-libという、Chainer開発元であるPreferred Networkが複数種類のGANsを実装、公開しています。主にこちらを使って画像生成を試してみています。

ただ、やはり少ない画像数でこれを行うことは難しいなあというのが実際やってみての感触です。

GANsでは生成器と識別器、2つのネットワークを交互に学習します。生成器はある分布に従う潜在変数の存在を仮定し、そこからサンプリングした乱数から画像を生成します。識別器は、生成器が生成した画像群を負例、訓練用に用意した画像を正例としてそれらを正しく分類するようなネットワークです。

生成器と識別器を交互に学習させることで、生成器は識別器の正例群が持つ画像の特徴を捉えた画像を生成できるようになる、というのがGANsの大まかな仕組みです。生成器と識別器はそれぞれ反対方向の目的関数に向かって最適化されるので、最終的にお互いの訓練誤差が均衡した状態が理想となります。

正例の画像があまりに少ないと、識別器の損失(誤差)がかなり小さな値にとどまってしまい、生成器は正例の画像をほぼそのまま覚えてしまうようです。つまるところ、「十分な数のデータがないと特徴を正しく捉えられない」というある種当たり前の結論に至りました。

人間だったら少ないサンプルからそれっぽいことが学習できるんですけどねえ…と思ったところでGANsの転移学習はできないのか、ということをこれを書きながら思いました。

何かしらの画像をうまく生成できるだけのモデルを予め学習させておき、それをベースにfine tuningすればもう少しマシな結果が出るのでは、という予想です。

これは一度試して見る価値はあるかもしれませんね…そのモデルを学習させるためのデータをどうやって用意するか、という課題はありますけど。

uim 1:1.8.6+gh20161003.0.d63dadd-6

unstableのuimを更新しました。以下の2つのRC bugを修正してあります。

前者の問題は、いくつかのパッケージ統合作業を行った時に、libuim-devのドキュメントをsymlinkに変えたことがトリガで発生した問題です。postinstで以下の処理を追加しました。これにより、symlinkが必要に応じて生成されます。

dpkg-maintscript-helper dir_to_symlink \
                /usr/share/doc/libuim-dev /usr/share/doc/libuim8 1:1.8.6+gh20161003.0.d63dadd-2.1 -- "$@"

後者の問題は 、uimバイナリパッケージに/var/lib/uimのみを所属させていたのがトリガです。パッケージ統合作業の時にlibuim-dataを廃止したのですが、このパッケージのpostrmではpurge時に”rmdir –ignore-fail-on-non-empty”を呼び出して/var/lib/uimを削除しようとします。uimパッケージに所属しているのはディレクトリのみなので、ここで削除されてしまうというわけです。

こちらの対処としては、/var/lib/uim/READMEを用意してディレクトリを空にしないという方向で解決しました。同様の処置をしているパッケージは他にもあるので、対処としては間違ってないと思います。多分。