shikoan’s memo

プログラミング初心者のチラ裏

ぷろぐらみんぐ帳

Coursera Machine Learningを終わらせてきた

前回の記事の続き。4/19に始めて5/14に終わりました。すごい楽しかったです。

ここのコースのまとめはQiitaに上がっています。だいたいこの記事と自分の感想は一緒ですね。とにかく初心者向けに丁寧に教えてくれます。全部英語ですが日本語字幕があるのがポイント高いですね。

qiita.com

自分は演習問題をPythonに移植しながらやったので1ヶ月近く(3週間ぐらい)かかってしまいましたが、早い人だと2,3日で修了してしまうケースもあるみたいです。ただ、非常に内容はしっかりしていて、かつこのコースの場合基本的に全部無料な(修了証でお金取ってる)ので、本当に初心者向けです。世界で一番わかりやすい機械学習の授業かもしれない。期間は3ヶ月ありますし、遅れても特にペナルティはなさそうなので気楽にやればいいです。どうしても間に合わなかったら次のセッションに引き継ぎもできるらしい。

f:id:shikoan:20180514080607p:plain

自分の場合は、小テストであまり粘らなかったので(4/5でさっさとクリアしてしまったので)100%はいきませんでした。ただプログラミングの課題こなすだけで結構もらえるんですね。

このコースで一番鬼門だったのがニューラルネットワークです。ただ、ニューラルネットワークを初歩から理解しようとするとどっちにしても難しいので、多少難しく感じてしまうのは致し方ないと思います。あとほかは思ったよりかは難しくなかったです。上記のレジュメを見たときはちょっと身構えましたが、Andrew Ng先生がすごくわかりやすい説明をしてくれるので全然平気でした。

自分の書いたPythonへの移植はこちらです。

次は強化学習の講義を見てみる予定。Courseraにあるロシア国立研究大学経済高等学院(ここ初めて聞いた)のか、Udacityか、あとYoutubeにあるUCLのDaivid Silver先生の授業でどれにしようか迷い中。

Courseraでスタンフォード大学に入学してきました

というのは半ばタイトル詐欺で、Courseraというインターネット上で講義が受けられるサイトがある。

www.coursera.org

ここの機械学習(Machine Learning)のコースが大変評判がよく、「機械学習をちゃんと勉強したいならここ」というのいうのをよく耳にした。

www.coursera.org

今期の機械学習の講義はちょうど4/16に始まったので、昨日から始めてみた。今第2週目の途中まで見ているところ。だいたいこんな感じ。

  1. 全ての講義のビデオも演習問題も無料でできる(少なくとも機械学習の講義は)。全ての演習問題をクリアしたときに「修了証」をもらうことができるが、これは課金が必要。要はここでマネタイズしているらしい。前まで50ドルぐらいで発行できたらしいのだが、4月現在8300円ぐらいになっていた(なので80ドル?)。Courseraのドル箱コンテンツなのでおそらく値上げ?
  2. 講義のビデオを見て演習問題を解いていくという形になる。ビデオは機械学習の講座は日本語字幕がある。なので感覚的には海外ドラマやドキュメンタリーを見ているのと変わらない。演習問題は英語なのだが、記述式でもなく選択式でたまに計算問題の答えを書かせるだけなので、わからなかったら翻訳にでもかけながら。あとプログラミングの演習もあるがこれは特に英語関係ないので。
  3. 文字通りビデオなので、寝っ転がって講義受けようが、お菓子食べながら見ようが、ゲームやりながら見ようが誰にも怒られない。最高すぎでしょ
  4. ちなみに修了証もらいたきゃ全ての課題をこなさないといけないので、1週目の課題締切の4/23までに始めないといけない(はず)。なので、機械学習始めたいなら今は絶好の機会。
  5. 機械学習は日本だと理系の情報科学のコースにあることが多いので、「数式がバーっと並べられてはい終わり」みたいな教え方がありがち。それを継承した書籍も似た傾向をたどっている。ただアメリカの大学なので(良くも悪くも)理系という概念がなく、向こうの想定としては「ベクトル行列わからない、微積わからない、簡単な方程式さえ解ければWelcome!」というスタンスらしい。確かに今や微積も行列もコンピューターで計算させるので、前よりは全然数学に対する障壁は低くなったが、それでもよくこんな敷居低くして教えられるなと思う。初学者に対してベクトルや行列、最適化ってこうやって教えるんだーと逆に参考になった。
  6. コースにログインすればわかるように、毎週そこそこ(4時間~7時間程度)の学習時間を要する。ただあくまでそれは動画の再生時間とレジュメの想定読了時間と演習時間を足し合わせたもので、「内容知ってるんでかったるいな」と思ったら、再生速度2倍にするなどスキップしながら見ればよい。なので、そこまで時間はかからない。もちろんよくわからなければ、想定時間を越えてわかるまでじっくり理解したほうがいい。少なくともこれらの内容を今日本にある教材(ネットや書籍など)でやろうとしたら、数ヶ月、半年は覚悟したほういい。もちろんそれは人によりけりなのだが、ベースとなる数学の復習をしようと思ったらそれぐらいかかる(そして日本にある本は数学がわからないと読めないし、数学を避けている本は漠然とした説明になりがち)。なので、講義の内容をじっくり理解したほうが結果的に早く習得できると個人的には思う。
  7. 担当のウ(Ng)教授の言葉を借りて言えば、「日曜大工で『これはのこぎり、これは金槌、これはドライバーです。じゃああとは頑張って』というのはよくない」だそうで、確かに巷に出回っている本を立ち読みしていると、理論はわかってもじゃあそれをどう応用し、現実での問題に適用していくかというのがわかりづらいことが多い。具体例が出ていても、ごく限られた例(著者の気持ちとしてはわからなくもないが、1つの本に出ている具体例が、ほぼMNISTの手書き数字のデータの分類という本があって、それを見たときは唖然としてしまった。別に手書き数字の分類だけがやりたくて機械学習を勉強しているわけではないし、そういうのはネットに情報が出回っているので独学でも全然できる。というかQiitaに書いた)であることが多い。また機械学習には大きく分けて、分類(Classification)問題と回帰(Regression)問題があるのだが、分類の例はわりと情報出回っているが、回帰が少ない。しかし、この講義の出発点は回帰問題の最小二乗法だ。シラバスを見ていても若干回帰を中心的にやっているように見える。実際の問題への適用というのはこれからの内容を見てみないとよくわからないが、かなり信頼できると思う。というか日本語の資料見るよりよっぽどわかりやすい。
  8. もともと統計の重回帰、ロジスティック回帰ぐらいは知っていたので2週目の時点では「へーあれを機械学習から見るとこうなるんだ。でも結構共通すること多いな」ぐらいの印象なのだが、もう少し進むとパーセプトロンSVMが出てくるので未知のエリアに入ると思う。

だいたいこんな感じ。3番目がポイント高め。また追加でわかったことがあれば記事書く予定。

AtCoderに登録してみた

前々から気になっていたAtCoderに登録してみた。Beginners Sectionの10問全てと過去問のA~Dの10問ちょいを解いてみた(あんまそこまで本気でやるつもりはない)。

abs.contest.atcoder.jp

AtCoder Problems

レートがつく大会に出ていないので晒すほどのプロフィールではない。探せば出てくるはず。

言語はC#で。C++推奨とか書いてあるがあんな黒魔術やりたくない。LINQ最高~。見える……ガバガバ実装でメモリや処理時間バカ食いする未来が!(言ったそばからBeginnersのC問題でTSEやMSEを出したで自分が通ります)

以下、noobのチラ裏なんで真に受けないでね!!

メモリの制約、実行時間の制約

競技プログラミングということで、メモリアロケーションの制約、実行時間の制約が大変厳しい。2秒で256MBの制限なら、1つのテストケースで0.1秒でもオーバーしたら不正解扱い、途中計算でちょっとでもメモリ使用量オーバーしたらアウト。「えー最後にGCで回収するしいいじゃん(バンバン)」は通用しない。ここが競技プログラミング、厳しい。
逆に言えば、採点ケースで通過すればいいので、実装は正しくなくても想定している答えを出せば正解扱いになる。極端に言えば、全てのテストケースとその答えを知っていたら、if文なりswitch文なりで入力に対応する答えを出力するだけで正解になる。ここが面白いところ。
あと回答時間の制約や不正解のペナルティもあるが、時間制約のあるコンテストをやったことがないのでなんともいえない。過去問見るとあれを時間制限ありで正解を解くのは結構きつい気がする。処理時間さえ許せば速く書けるほうがいいので、あとで見返した際の可読性はよろしくないと思う。100点の美しいコードをじっくり書くよりも、70点ぐらいで速くかけるほうを目指したほうがよさそう。

競技プログラミングではないが、似たようなプログラミングで問題を解いていくサイトとしてProject Eulerがある。こっちは昔やっていたことがあって時間をかければ150問ぐらいは解けた。

projecteuler.net

こっちはメモリ制約も時間制限もなくて答えを入力するだけ。想定としては「上手く実装すれば全てのケースで1分以内に処理が終わりますよ」とのことなのだが、そんなのチェックしていないし(なぜなら問題の回答の値を1個入力すればいいだけなので)、裏でこっそり1日ぐらい処理を回してようが、メモリをギガバイト単位で使っていようがばれっこない。回答としては美しくないのだろうけど。じっくり派の人はこっちがいいかもしれない。ただオイラーの名の通り数学要素が強いので、好き嫌いはあると思う。

難易度別雑感

ABC(AtCoder Beginner Contest)での難易度基準です。

A問題、B問題:プログラミングある程度やっている人なら一目で解けるやつ。B問題は一瞬ん?と思うけどああ、そうやるのねってなる感じ。将棋で言う所の詰将棋の1、3、5手詰めぐらいの感覚だと個人的には思う。
ただプログラミング初めてすぐの人だとこれ解くのも結構苦労すると思う。A問題、B問題の知識は競技プログラミングやらずに、普通にプログラム書く場合も全然使うので、単なるプログラミング学習教材としてもかなり有効。ここらへんが一目で解けるようになったらもうプログラミング初心者じゃない。大学でプログラミングの入門講義があるとしたら、AB問題余裕で解けるようになったら一切出席しなくても単位がくるレベル(もちろん出席点で足切りされたら知らないし、AtCoder1日やっただけのガバガバ基準なので自己責任で!)。

C問題:ちょっとトリッキーになるが、整理すれば解ける。問題によってはD問題より難しい(というか慣れてなくて、テストケースで通っても採点のケースでハマるというのがよくある)。Cぐらいになると計算量を頭の片隅ぐらいにおいておかないといけない。詰将棋でいうところで5手~9手ぐらいなのかな。C問題は解けても9手詰めなんてまず解かないからここらへんの感覚はよくわからない。
ただ、競プロの強い人のを読んでいると、最初はアルゴリズムよりもデータ構造で殴ればよさそうなので、データ構造の話なら普通のプログラミングでも結構生きると思う。例えば、どんどん末尾に追加していく処理で、リストじゃなくて配列を新たに作って追加するのやめちくり~~という話(まさかこんなことをする人はいないとは思うけど、世の中にはわれわれの想像を下回るウンコードが存在するので侮ってはいけない)。アルゴリズムに興味なくても、計算量はやはりどこか頭の片隅においたほうがいい。

D問題:E以降は解いていないが、ここは得点によって難易度が違うように感じた。配点100点や200点ぐらいのはものによっては一目なのもあるが、600点の問題は取っ掛かりすらつかめないのもある。というかどう解くのか教えて。
ここらへんからはアルゴリズムを知っていかないとダメな感じはする。こういうのは普通にソフト作ってても覚えられない。「なら、何の役にも立たないじゃないか!」と思うかもしれないが、組み合わせ爆発に立ち向かう姿勢はとても意義のあるものだし、いざというときのブレイクスルーにはなると思う。少なくともこういうので殴り合う世界ではあってもいい。

プログラミングはじめての人はAB問題中心に解けばいいし、腕に覚えがある人はCD問題中心に解けばいい。なるほど、初心者も上級者も楽しめるようによくできている。

覚えてみればコンテストのあるときに腕試しで出て見るかもしれない。意識が低すぎるので忘れてて時間すぎているか、運良く覚えていて出れたとしてもガチ勢にボッコボコにされる未来しか見えない。CD問題は暇な時に解いてみたい。

何がいいたかったかというと、A問題B問題解ければ一般人よりはかなりプログラムできますよということ。

おまけ:「組み合わせ爆発」でググってたら科学未来館が意味深な動画を出していた。結構有名?

www.youtube.com

Numpyで相関行列

テスト用のデータを作成。

import numpy as np

# テストデータ
x = np.arange(5).reshape(5,1)
print(x)
print()
[[0]
 [1]
 [2]
 [3]
 [4]]

このまま複製して相関係数を計算すると「標準偏差が0だゴルァ」と怒られるので、ノイズを乗っける。平均0、標準偏差0.5の正規乱数を各20個、合計100個用意する(再現性を出すために乱数シードを設定)。

# 乱数のシード設定
np.random.seed(123)
# ノイズを乗っける
noise = np.random.normal(loc=0.0, scale=0.5, size=100).reshape(5, 20)
data = x + noise
print(data)
print()
[[-0.5428153   0.49867272  0.14148925 -0.75314736 -0.28930013  0.82571827
  -1.21333962 -0.21445631  0.63296813 -0.4333702  -0.33944308 -0.04735448
   0.74569481 -0.319451   -0.22199098 -0.21717564  1.10296504  1.09339304
   0.50202695  0.1930932 ]
 [ 1.36868429  1.74536601  0.53208307  1.58791452  0.37305967  0.68112425
   1.4535526   0.28565965  0.92996564  0.56912255  0.87219031 -0.39929455
   0.11423345  0.65006138  1.46373122  0.91318216  1.00142296  1.34411136
   0.56023183  1.14181366]
 [ 1.59731674  1.13616525  1.8045501   2.28690293  2.16929453  1.99408475
   3.19618263  2.20645608  2.489368    3.11907167  1.35295734  1.48060589
   2.87185611  1.60096863  2.01484162  2.53465798  2.4453532   2.87744309
   2.74782207  2.53469633]
 [ 2.61364564  3.39743133  3.157136    2.33686727  3.70864952  3.40361827
   3.02274504  2.88345397  2.40084943  3.09976204  3.23421956  2.58442251
   3.58110202  2.45139848  1.93844982  3.51986355  2.79831698  2.93698521
   2.58124164  2.19701862]
 [ 4.62761869  3.65556551  4.83047624  4.40365409  3.84262093  3.4570488
   3.63376901  3.39373843  5.04355668  4.08222062  4.57510277  3.36632398
   4.09051756  4.58893097  3.83249462  4.51555723  3.45771604  3.31826423
   4.18970031  3.81041178]]

相関行列は以下の通り

# 相関行列
C = np.corrcoef(data)
print(C)
[[ 1.         -0.1302316   0.05565825  0.12951066 -0.24792661]
 [-0.1302316   1.          0.0090564  -0.26099422  0.07399095]
 [ 0.05565825  0.0090564   1.          0.03105442 -0.14340739]
 [ 0.12951066 -0.26099422  0.03105442  1.         -0.10365978]
 [-0.24792661  0.07399095 -0.14340739 -0.10365978  1.        ]]

相関行列の[0,0]はdata[0]とdata[0]の相関係数で、相関行列の[0,1]はdata[0]とdata[1]の相関係数……と続く。data[i]とdata[i]の相関係数は1なので(全く同じデータだから)、相関行列の対角要素は必ず1になる。

C#で標準(エラー)出力をリダイレクトする

前置き。月額200円ぐらいでWindowsServer2016が使えるVPS環境が見つかったのでお遊び。バックグラウンドで動かすプログラムがあるのだが、たまに落ちてしまうことがあるので、定期的に「実行していなければ動かして、動いていれば何もしない」という操作をした。

タスクスケジューラ

これはLinuxは言う所のcronを使えば簡単に(?)できる。ワンライナーでできるらしい。Linux環境ではPythonが使えれば、モジュールのpsutil使って、プロセスを確かめていって同時起動を防止することもできる(自分はこっちのほうが好き)。Windows環境の場合、cron相当のものとしてタスクスケジューラがあり、ほぼ同様のことができる(cronと違って分単位の細かい設定をするのが難しい)。以下、設定する際のポイント。

タスクスケジューラの作業ディレクトリの設定

cronのときにありがちなミスとして、コマンドラインからは実行できたが、cronから動かすと実行できないというものがある。大方パスがらみで、cronの場合はカレントディレクトリが変わるため、気をつけないと相対パスが処理できない。cronの場合の解決法として、

  1. 絶対パスで記述する → 最も確実だがデバッグ環境でパスを書き換えるのがめんどい
  2. シェルスクリプトを噛ませて、プログラムを実行する前にcdでカレントディレクトリに移動しておく → 結構使える
  3. 例えばPythonの場合はプログラムレベルで設定する → これも好き
import os
os.chdir("カレントディレクトリ")

タスクスケジューラの場合は、設定画面で作業ディレクトリを設定できる。これを忘れていて最初ハマったが、ここで設定したらバッチファイルが通った。

f:id:shikoan:20180408075418p:plain

開始(オプション)に作業ディレクトリを設定する。地味にわかりづらい。

分単位での発動のさせ方

トリガーで編集する。毎日1回としておいて、トリガー→編集として繰り返し間隔で分単位で設定できる。詳細はこれ↓

www.atmarkit.co.jp

ただ、5分、10分、15分、30分、1時間のような既定値でしか設定できないようで、cronのような細かい設定はできない。ここだけはcron記法で記述したかったり。

セキュリティオプションによる違い

タスクスケジューラのセキュリティオプションとして次の2つがある

  • ユーザーがログオンしているときのみ実行する
  • ユーザーがログオンしているかどうかにかかわらず実行する

そりゃcronの代用として使うんだから下一択でしょと思ったら、なんとこのオプションで動作の仕様が変わる。下を選ぶとバックグラウンドプロセスになり、コンソールアプリケーションの場合は黒い画面が表示されない。 なら上を選べばいいか、というと、ログオンの定義が最初「該当のユーザーでログインしていればいい、つまりリモートデスクトップを閉じてしまっても別のユーザーでログインしなければ大丈夫」という意味かと思っていたら、実行されなかった。なのでここでの、ログオンの定義=リモートデスクトップ等で該当ユーザーのセッションが維持されているという意味だと思う。

なので結局下を選ぶしかない。ところが、バックグラウンドプロセスになるので、コンソール画面が表示されず何やっているのかよくわからない。ならどうするか、解決法の1つとして、出力をファイルにリダイレクトしてしまえばいいのです。シェルなんかではおなじみのテクニック。

バッチファイルでもできるけど、せっかくWindowsServer使っているからC#で記述してみた。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.IO;

class Program
{
    static string filePath = null;
    static Encoding utf8 = new UTF8Encoding(false);

    static void Main(string[] args)
    {
        var p = new Process();
        //ログファイルのディレクトリを作成
        var date = DateTime.Now;
        filePath = Path.Combine(Environment.CurrentDirectory, "log", date.ToString("yyyy"),
            date.ToString("MM"), date.ToString("dd"),
            date.ToString("s").Replace(":", "_") + ".txt");
        var dir = Path.GetDirectoryName(filePath);
        if(!Directory.Exists(dir))
        {
            Directory.CreateDirectory(dir);
        }
        //出力をストリームに書き込むようにする
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardError = true;
        //OutputDataReceivedイベントハンドラを追加
        //p.StartInfo.RedirectStandardOutput = true;
        //p.OutputDataReceived += P_OutputDataReceived;
        p.ErrorDataReceived += P_OutputDataReceived;
        p.StartInfo.RedirectStandardInput = false;
        p.StartInfo.CreateNoWindow = true;

        p.StartInfo.FileName = Path.Combine(Environment.CurrentDirectory, "実行するプログラム");
        p.StartInfo.Arguments = @"引数1 引数2 ……";

        //起動
        p.Start();

        //非同期で出力の読み取りを開始
        p.BeginErrorReadLine();
        //p.BeginOutputReadLine();

        p.WaitForExit();
        p.Close();
    }

    //標準出力と標準エラー出力を同時に読むときは、標準出力読んでいるときにエラー出力が割り込む(あるいはその逆がある)ので
    //ファイル操作部分をロックする。どっちかだけの場合は不要
    private static void P_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (filePath == null) return;
        //追記してBOMなしUTF8
        using (var sw = new StreamWriter(filePath, true, utf8))
        {
            sw.WriteLine(e.Data);
        }
    }
}

WindowsServerは何もインストールしなくても.NETのプログラムが動くのが素晴らしい(MS製品だからそれはそう)。期待通りの出力になった。実行しているプログラムが、ログを標準出力ではなく標準エラー出力に出すというちょっとよくわからないことをしているので、エラーのみを読んでいる。標準出力のみ読んでいたら何も出てこなくてびっくりした。

エラーと出力を両方読む場合は、必ずファイルをロック(排他制御しよう。usingの部分をlockのステートメントで囲ってしまえばいい。こんなイメージ↓

static _lock = new object();
static void Method()
{
    lock(_lock)
    {
        using(…)     
    }
}

実はこのプログラム、だいたい期待通りに動くんだけど100点ではなくて、親プロセスを終了させたときに、子のプロセスがゾンビプロセスとして生き続ける仕様がある。解決法はJob Objectを使うらしい。

stackoverflow.com

ざっと読んでみたらなかなか闇が深そうなので、スルーしてしまった。自分用だしゾンビプロセスは手動でkillすればいいよね、うん(おわり)。

iKoulaの専用サーバーでGPUマイニングをしてみた

以前、この記事でフランスのサーバー会社iKoulaの専用サーバーを借りた話をした。

koshian2.hatenablog.jp

ちなみに現在半額のプロモコード配布中で、初期の1~3ヶ月分がだいたいのプランで半額になる。自分が借りたE3 1220v5のプランは月額14.99€+VAT(付加価値税)。このキャンペーンは4月末までやっている。CPUは使っているのだが、これにはGeForce GT 710というGPUが申し訳ばかりについているので、そのまま眠らせておくのも勿体無いなと思って、GPUマイニングをさせてみた。

モナコインを掘る

OSはUbuntu16.04。とりあえずCUDAをインストールしておく。他いろいろ書かれているのでめんどくさいから説明はここらへん読んで。CUDAバージョンは8.0にしておいた。

そもそもこのGT710というグラフィックボード自体相当ローエンドなので、最新のCudaを使っても計算の最適化はされない。それどころかCuda9.0以降ではローエンドのGPUで使えるcompute_20というモードがサポートされていないため(Cuda8.0でビルドしていても”近々サポートされなくなります”という趣旨の警告がずらっと出てくる)、Cuda8.0にしたほうが良さそう。 別にccminerに限った話ではないが、compute_○○の設定はMakefile.amのnvcc_ARCHでする必要があり、これをうまく設定しないと十中八九ビルドでコケる(CUDAの他にもc++のビルドでコケることもあるので結構ビルドがめんどい)。

いろいろマイナーはあるが、このccminerしかビルドできなかった

github.com

ccminerもフォークがいろいろあり、例えばyescrypt対応版の改造版(調べれば出てくるので適当にググって)や、全く別のマイナーのcgminerやClaymore(全てが全てモナコインを掘れるわけではないだろうが、他のコインも掘れるかなと思って試してみた)もビルドで失敗。ccminerのインストールページより、

ccminer/INSTALL at linux · tpruvot/ccminer · GitHub

** How to compile on Ubuntu (16.04 LTS)

First, install Cuda toolkit and nVidia Driver, and type `nvidia-smi` to check if your card is detected.

Install dependencies
sudo apt-get install libcurl4-openssl-dev libssl-dev libjansson-dev automake autotools-dev build-essential

Ubuntu is now shipped with gcc 6 or 7 so please install gcc/g++ 5 and make it the default (required by the cuda toolkit)

sudo apt-get install gcc-5 g++-5
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 1

Then use the helper ./build.sh in ccminer source folder, edit configure.sh and the Makefile.am if required.

./build.sh
./ccminer --version

このへんサクッとやるのだが、そのままビルドしたらコケたので、Wikiに従ってMakefile.amの設定でアーキテクチャーの追加

Compatibility · tpruvot/ccminer Wiki · GitHub

compute_○○というのはGeForceCompute Capabilityに対応するのだと思われる。この値はここで見れる。GT710の場合はよく情報が載っていなく(GT710の公称スペックではメモリは2GBのはずだが、サーバーで提供していたGPUが1GBしかなかったのでん?ちょっとよくわからないなー…まあいいやという感)、710MならCC2.1なのだが、ググってるとCC3.5という情報もあったのでこうしてみた。

#nvcc_ARCH += -gencode=arch=compute_61,code=\"sm_61,compute_61\"
#nvcc_ARCH += -gencode=arch=compute_52,code=\"sm_52,compute_52\"
#nvcc_ARCH += -gencode=arch=compute_50,code=\"sm_50,compute_50\"
nvcc_ARCH += -gencode=arch=compute_35,code=\"sm_35,compute_35\"
nvcc_ARCH += -gencode=arch=compute_30,code=\"sm_30,compute_30\"
nvcc_ARCH += -gencode=arch=compute_20,code=\"sm_21,compute_20\"

これでCC3.5~2.1のアーキテクチャー全てに対応できる。実際compute20だけの場合と、compute30と35を含めた場合だと、含めた場合のほうがハッシュレートが15%ほど向上していたので、多分CC3.5なんじゃないかなと思われる。このMakefile.amを更新したらビルドできた。CUDAを使ってビルドするのでこれがえらく時間かかる(5分はかかった)。ここらへんのビルドがくっそ面倒くさいのがGPGPU特有の現象で面白い。

プールの登録はVIP poolを使ってサクッと。バックグラウンド+ログアウトしても動かすようにするには、(nohup.outへの出力がウザければ > /dev/nullとかで捨てちゃえばいい)

nohup ./ccminer -a lyra2v2 -f 0.1 -o stratum+tcp://vippool.net:8888 -u [username].[worker] -p [password] -t 1 &

ハッシュレートは580kH/s程度。メガ乗らなかった残念。流石にこんだけローエンドなモデルではクソ雑魚だった(ですよねー)。半日放置してなんと1万分の2.5モナコインが生成された。ちなみに現在1モナコイン=350円程度なので、1日放置して0.2円行くか行かないかぐらい。ハハッ、ワロス(死語)

ちなみに、CPUとGPUの同時マイニングできる。フランスの専用サーバーを借りて同時マイニングとかネタ以外の何者でもない。

MinerGateを使って採掘

さすがにこれでは引き下がれないので、MinerGateも試してた。こっちはビルド簡単でした。結果だけ。

CPU:E3 1220v5 @3.0GHz 4Core GPU:GeForce GT 710 1GB

モネロもバイトコインも同じCryptoNightアルゴリズムなのでほぼ同じ結果。

CPU4コア:160H/s GPU:30H/s

まさかGPUよりCPUのほうがハッシュレートが上とは。CryptoNight自体がメモリ依存のアルゴリズムなので、GPUのメモリが1GBなのがきっと悪いのだろうね。ちなみにこんなアルゴリズムの概要。日本語記事だとろくな解説ないから英語記事を当たろう。英語版Wikipediaには詳細な解説あり。

Principles
CryptoNight relies on random access to the slow memory and emphasizes latency dependence. Each new block depends on all the previous blocks (unlike, for example, scrypt). The algorithm requires about 2 Mb per instance:

  1. It fits in the L3 cache (per core) of modern processors.
  2. A megabyte of internal memory is almost unacceptable for the modern ASICs.
  3. GPUs may run hundreds of concurrent instances, but they are limited in other ways. GDDR5 memory is slower than the CPU L3 cache and remarkable for its bandwidth, not random access speed.
  4. Significant expansion of the scratchpad would require an increase in iterations, which in turn implies an overall time increase. "Heavy" calls in a trustless p2p network may lead to serious vulnerabilities, because nodes are obliged to check every new block's proof-of-work. If a node spends a considerable amount of time on each hash evaluation, it can be easily DDoSed by a flood of fake objects with arbitrary work data (nonce values).

ただ、MinerGateの計算機で計算すると、1週間で0.00002 BTC。1BTC=70万円で計算しても、1週間で14円。これでもモナコインの10倍近い効率は出ている。

サーバーの余剰な計算資源を走らせておくならこんなものだろう。もちろんiKoulaにはマイニングに特化した専用サーバーもあって(iKoulaの公式Wikiを見てるとわざわざ「マイニングにおすすめ」と書いてある)、

Intel® Xeon® E3 1230v5 1 CPU (4C/8T) @3.4 GHz
16 GB DDR4
1 TB SATA
Nvidia GeForce GTX1070 8 GB GDDR5 / 1920 Core セットアップ費なし、月額€139.99

適当にハッシュレートを探して計算してると、イーサリアムあたりなら週に1000円ぐらいになるらしい。ちなみに140€は19000円程度なので、普通に赤字になると思う。ネタ的には美味しいので誰か勇者借りてみて(丸投げ)

GPUならまだしも、ASICがボロボロ出てくると(CryptoNight対応のASICが最近出てきたらしいという噂)もはや大人の殴り合いで個人は蚊帳の外なので、GPUというかCPUでマイニングしているような牧歌的な原風景が好き。そういう点では国産コインはASIC耐性ありでまだまだGPUの魔の手が及んでいないものも多い(おそらく上に挙げたように国産より流動性がよく、GPUでできてまだ黒字のやつがあるからそっちに行っているのだと思う)ので、面白いジャンルではあると思う。scryptの例であるが、GPU耐性についてはこの記事が面白かった。

blog.visvirial.com

がっつりと仮想通貨の話を書いてしまったが、儲かる儲からないだけではなく仮想通貨の面白い話をするならこのぐらい突っ込んで書かないと物足りない。

NumPyの行列演算で二重ループをなくす

100 Numpy Exercisesの86番を見て知ったテクニック。これは数値を2進数表記する問題。

86.Convert a vector of ints into a matrix binary representation (★★★)

# Author: Warren Weckesser
I = np.array([0, 1, 2, 3, 15, 16, 32, 64, 128])
B = ((I.reshape(-1,1) & (2**np.arange(8))) != 0).astype(int)
print(B[:,::-1])

# Author: Daniel T. McDonald
I = np.array([0, 1, 2, 3, 15, 16, 32, 64, 128], dtype=np.uint8)
print(np.unpackbits(I[:, np.newaxis], axis=1))

2番目のほうが直感的でわかりやすいかな。内容はまあいいとして、1番目の2行目で使っているテクニックがん?となった。わかりやすいように別の例に変える。

>>> x = np.arange(5)
>>> x
array([0, 1, 2, 3, 4])
>>> x.reshape(-1,1)
array([[0],
       [1],
       [2],
       [3],
       [4]])

xとx.reshape(-1,1)って演算することってできるんだ! 数学的には正しい演算ではないだろうが(演算子が*の場合、ベクトルの外積になるらしい。np.outer(np.arange(5), np.arange(5))のように計算できる。なのでその拡張ととらえても良さそう)、プログラム上では結構便利な機能かもしれない。上記の例では論理積だったが、四則演算も当然できる。

>>> y = x*10
>>> y
array([ 0, 10, 20, 30, 40])

>>> x.reshape(-1,1)+y
array([[ 0, 10, 20, 30, 40],
       [ 1, 11, 21, 31, 41],
       [ 2, 12, 22, 32, 42],
       [ 3, 13, 23, 33, 43],
       [ 4, 14, 24, 34, 44]])
>>> x+y.reshape(-1,1)
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24],
       [30, 31, 32, 33, 34],
       [40, 41, 42, 43, 44]])
>>> y.reshape(-1,1)+x
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24],
       [30, 31, 32, 33, 34],
       [40, 41, 42, 43, 44]])
>>> y+x.reshape(-1,1)
array([[ 0, 10, 20, 30, 40],
       [ 1, 11, 21, 31, 41],
       [ 2, 12, 22, 32, 42],
       [ 3, 13, 23, 33, 43],
       [ 4, 14, 24, 34, 44]])

結果の違いは味わいがいがある。行列の和差は交換法則が成り立つので、reshapeするのに気をつければあんま気にしなくてもいいかなと思ったり。この手の処理は手続き脳だと二重ループで書いてしまいたくなるが、このやり方だと結構簡潔に書けそうだ

Python使いがループを外しの職人芸をしたくなる気持ちがよくわかる。

この手の処理がループのシンタックスシュガーのように見えて気持ち悪いかもしれないが、そもそもNumPy自体がCのネイティブコードを動かして高速化を図ったものでPythonとは似て非なるもの。Cから見ればNumPy自体が広義のシンタックスシュガー(んなこと言ったら高級言語はみんなそうなるけど)、Pythonのforループは遅いということを先入観として叩き込まれている身としては(Rがそうだったからなんとなくしっくりくる)、ループのような処理は低レベルの言語に任せっきりにしてもいいのではないかなと思う。少なくともそれで速くなるのなら、NumPyの独特の記法にならってループを外したほうがいい。

atsuoishimoto.hatenablog.com

追記:この記事によると、Pythonのループ処理が遅い犯人はfor文ではなく、どちらかというとPythonが静的な型宣言を行わないからだそうだ。読んでいてへーっとなった。