プログラミングと絵と音楽

コンピューター科学を専攻し、絵と音楽を趣味とするエンジニアのブログです。

Dockerコンテナ内のlocalhost起動に気をつける

問題

Rubysinatra で、APIサーバー (server.rb) を作っていました。

ReactJS と共存させるために Docker へ移行していたのですが、少し詰まってしまいました。

docker-compose.yml で

api:
  command: ruby server.rb
  ports:
    - "12345:4567"

と mapping を設定することで、外部から ttp://(ホスト名):12345" で読み込めるはずだったのですが、接続できません。

ホストで curl localhost:12345 を実行しても、

curl: (56) Recv failure: 接続が相手からリセットされました

を乱発することに。

ただし、 docker コンテナの中に入って

curl localhost:4567

とすると問題なく反応しています。

解決

ローカルで実行していたときは、実行時のコマンドで

ruby server.rb -o 0.0.0.0 -p 12345

としていたのですが、ポートは docker-compose.yml でマッピングできるからと、引数 (-p 12345) を消していました。
このとき、ホスト (-o 0.0.0.0) の部分まで消してしまっており、そこが問題の発生に繋がりました。

通常ならすぐに原因は見つかるはずだったと思うのですが、今回は Docker 移行の際にこの問題に直面し、 Docker に関する問題と疑ってかかってしまったため、調査が長引く結果となりました。

Docker コンテナ内でも、 localhost で起動するのではなく、 ホストを 0.0.0.0 とすることが必要です。

api:
  command: ruby server.rb -o 0.0.0.0
  ports:
    - "12345:4567"

で無事繋がりました。

初めてのノイズキャンセリング体験: AirPods Pro

ネットで注文した AirPods Pro が届きました。

発売後、店頭 Apple Store に行っても売り切れの状態がずっと続いていたため、ネットで注文するほうが確実で早いだろうと思い、ネットで注文しました。
到着までに1ヶ月弱かかりましたが、それでも現在も店頭で売り切れているため、良い判断だったと思います。

ネット注文限定の、刻印も入れてもらいました。

f:id:tfull:20200308163400j:plain
刻印入り AirPods Pro

以下、良かった点と気になる点です。

良かった点

素晴らしいノイズキャンセリング機能

ノイキャン自体、体験するのは初めてでしたが、感動するものでした。

細かいノイズなどは全部排除されます。外の大きい声や、目の前で手を叩く音などは、音の小さい状態になって届きます。

フィット感

カナル型で、耳にとてもフィットします。あまり落ちる心配をしなくても良さそうです。

切り替えが簡単

ノイズキャンセリング、外部音取り込み、オフの中からモードが選べ、接続した Apple 端末で簡単に切り替えられます。

Apple 製品との連携

他の Apple 製品でもそうですが、ペアリングを切り替えたりするのが簡単(Bluetooth画面から AirPods Pro をタッチするだけ)なのは、複数の端末を使っている私にとっては魅力的です。

気になった点

LとRを間違えやすい

ノイズキャンセリングや外部音取り込みが勝手にオフに切り替わるので疑問に思っていたら、LとRを逆にセットしていました。個人的には、

高い

イヤホン3万円は気軽に買える値段ではないですし、小さいため紛失しないか心配になります。

Google Nest Wifi が素晴らしく快適だった

Wifi 環境を改善するために、 Google Nest Wifi を購入したら、予想以上に良かったので、まとめたいと思います。

f:id:tfull:20200307222756j:plain
Google Nest Wifi

ネットワーク環境の変化

今までは、モデムのある書斎かつ寝室である部屋に通常のルーターを配置し、離れたリビングに中継ポイントを配置していました。

Google Nest Wifi を購入して、ルーターと中継ポイントをおいていた箇所に、入れ替えるようにルーターと拡張ポイントを配置しました。

良かった点

コンパクト

大きさがコンパクトなので、机の下に置いていてもあまり邪魔に感じません。
また、丸っこく背が低くて安定しているため、倒れる心配がない、足がぶつかって痛いなどもありません。

通信が頑健で途切れない

以前のルーター+中継ポイントは、書斎で使う文には問題ありませんでしたが、書斎からリビングまでの間にあるドアを挟んでしまうと、通信がかなり不安定になるという問題がありました。したがって、その間のドアを開放して使うという、「運用でカバー」状態になっていました。これを Nest Wifi にすることで、ドアを挟んでも通信が安定しており、リビングの大きいテレビで Nintendo Switch の通信対戦などを行うのが快適になりました。

また、以前は電子レンジを使っているときに電波が途切れてしまうという問題があり、電子レンジを使うのが億劫になる場面があったのですが、こちらも解決し、懸念点を一つ払拭することができました。

SSID が統一されている

まず、ルーターに2.4 GHz と 5 GHz の SSID. そして、中継ポイントに別 SSID で 2.4GHz, 5GHz があるため、4つのSSIDを登録して使い分けるような運用になっていましたが、自身で Google Home を用いて設定した SSID とパスワードによって、一つのSSIDを登録すれば使えます。SSID とパスワードを自分で設定できるため、覚えやすい名前をつけられるのもメリットの一つです。

Google Home が使える

拡張ポイントの方は Google Home が使えます。「OK グーグル」で、今日の天気を聞いたり、(YouTube Premium に入っているので)YouTube Musicを流したり、思いの外これが役に立っています。思いの外便利だったため、書斎に Google Home Mini を追加で買いました。携帯に入れた Google Home からブロードキャスト機能で伝言を発したりもできます。

気になる点

高い

ルーターと拡張ポイント1つのセットで 32,000円かかりました。ルーターにしては決して安くはない買い物だと思います。

有線が一つしかない

Nest Wifiルーターには、モデムから繋げる WAN が一つと、 端末に繋げる LAN が一つです。 LAN が一つしかないということで、複数の PC をつなげようとすると、スイッチングハブが必要になります。私は TP-Link の5口のスイッチングハブを購入しました。

拡張ポイントの QR コードは大事

拡張ポイントにくっついていた QR コードのシールを開封時にゴミ箱に投げ捨てていたのですが、設定のときに必要です。気づいて回収しましたが、これが無くて、本来苦労するはずのない設定に苦戦しました。いずれ再設定のときも必要になりそうなので、セロテープで拡張ポイント本体にくっつけています(ダサい)。

まとめ

高い買い物であったものの、 Google Nest Wifi を購入して、ネットワーク環境がかなり快適になりました。今のところ、気になる点は解消できているため、買ってよかったと思っています。

docker内のnodeでfsに関するエラーが出た

dockerを使ってnodejs で開発しているとき、npm startで

Error: ENOSPC: System limit for number of file watchers reached ...

というエラーが起きました。監視しているファイル数が多すぎるエラーのようです。


docker内でエラーが起こったため、Dockerfileに変更を加えようと思ったのですが、このファイル監視については、ホスト側の設定がそのまま使われるようです。

Dockerfileはいじらずに、

次のように設定を追加して反映すると良いようです。

sudo echo fs.inotify.max_user_watches = 524288 >> /etc/sysctl.conf
sudo sysctl -p


確認は

cat /proc/sys/fs/inotify/max_user_watches

によってできます。

この数値についての根拠は把握していません(これが限界らしい)が、デフォルトの8192より十分大きいので問題ないと判断しました。

これで npm start がきちんと動作するようになりました。

質問応答システムの実装と考察:BOWとTFIDFによる検索

まずはシンプルな手法として、文章を語句に分割し、それを比較する計算を行い、最もスコアの高い Wikipedia 記事のタイトルを回答として出力してみようと思います。

ここでは、 Bag of Words と TF-IDF法を用います。

キーワード

  • Bag of words (BOW)
  • TF-IDF

回答の候補となる記事を絞る

これからやる手法では、238万の記事を全部調べるには相当な時間がかかるため、用意した質問の正解となる記事と前後100記事で、約1600の記事に絞りました。現実的な時間で238万記事から正解を導き出すには、PCのスペックを上げたり、複数の計算機で並列に動作させる必要があります。

質問文章の分解

文章を語句に分割します。私は今まで MeCab という形態素解析器を使っていましたが、コマンドライン1行で導入できる janome を使ってみました。

shell$ pip install janome

janomeで単語に分割し、それぞれが何回出現したかと一緒に記録しておきます。

入力

戦国時代の武将であり、本能寺で織田信長を討ったのは誰?

Bag of Words

{'戦国': 1, '時代': 1, 'の': 2, '武将': 1, 'で': 2, 'あり': 1, '、': 1, '本能寺': 1, '織田': 1, '信長': 1, 'を': 1, '討っ': 1, 'た': 1, 'は': 1, '誰': 1, '?': 1}

候補となる記事を抽出

質問を分解した中で出てきた単語と、各記事の文章を分解した中で出てきた単語のうち、共通する単語を含むものを全て回答の候補として抽出します。上記の質問であれば、単語「戦国」含む記事、単語「時代」を含む記事、以下同様というようにです。

しかし、これでは殆どの記事が助詞「で」、「は」などを含んでいるため、それら全てが候補になってしまい、候補が激増してしまいます。

そこで、記事全体のX%以上に含まれる単語は、候補の検索から除外しました。具体的な値は何通りか試しましたが、X=40 (%) だと助詞とかがちょうどよく消えてくれました。

スコアの計算

検索した各記事に対して、類似度を計算します。

記事のそれぞれの単語 w_i について、次のようにTFとIDFを計算します。

Term Frequency

式だと次のようになります。
\displaystyle \mathrm{TF}(w_i) = \frac{w_i}{\sum w_i}

例えば、全部で100単語からなる文章で、「りんご」という単語が3回出てくる場合、「りんご」のTFは3/100ということになります。

これは、同一文章でよく現れる単語は重要度が高いという概念の数値化です。

Inverse Document Frequency

 \displaystyle \mathrm{IDF}(w_i) = \log \frac{D}{| \{ d\, | \, d \ni w_i \} |}

Dは記事の全体数、dは各記事になります。対数の中の分母は、単語w_iを含む記事数です。

例えば、今回使う1600記事の中で、「りんご」という単語が31記事に含まれていた場合、「りんご」のIDFはlog(1600/31)となります。

これは、特定の記事でしか出ない珍しい単語ほど重要度が高いという概念の数値化です。
一例として、助詞「で」「は」などは沢山の記事で出現するため、値が小さくなります。

コサイン類似度

各文章を、各単語を一つの次元とするベクトルとみなし、次の計算をします。

\displaystyle \cos{\theta} = \frac{\overrightarrow{q}\cdot \overrightarrow{e}}{|\overrightarrow{q}||\overrightarrow{e}|}

この cos θ の値をスコアとします。

実験

さて、質問を入力してみましょう。

> 戦国時代の武将であり、本能寺で織田信長を討ったのは誰?

スコアの上位5つが次のようになりました。

明智光秀 0.1510266237438856
1559年 0.13835019894259815
藤堂高則 0.1237466003784546
大永 0.11303296823717446
今川義元 0.10349135728955096

約1600記事の中のランキングではありますが、明智光秀を回答候補の1位として得ることができました。これは理想的な結果です。

もう一つ質問を入力してみます。

> 任天堂が2017年3月3日に発売した、据置でも携帯でも使えるゲーム機は何?

スコア上位5つ

コンピュータゲーム 0.28347233252398524
ゲーム 0.2329463227350586
Nintendo Switch 0.1985601368230161
ゲームのタイトル一覧 0.16659560167400825
はむばね 0.09190483488140566

期待した回答である Nintendo Switch は3位にきました。他の候補は、ゲームの中でも抽象的な単語が来ています。これは理想的な結果ではありません。

今後の課題

時間がかかる

一つの質問をするだけで1分以上かかってしまいます。約1600記事でこれですから、全体の238万記事になると現実的な時間で終わりそうにありません。

今の動作環境が 4 core 2.5 GHz, 4 GB RAM で貧弱であるのもありますが、現実的な拡張でも速度2~8倍が限度だと思いますので、アルゴリズムを根本的に変える必要があります。

文脈を考慮できていない

質問文を単語に分割し、比較するという手法をとっています。BOWでは順番などは考慮されていないため、語の形容や、主述の関係は消滅しています。これにより、例えば、織田信長を討った明智光秀か、織田信長が討った今川義元かを判別できません。連続するn語を塊として考えるn-gramというのもありますが、それで大幅に精度が向上するかは微妙なところです。

表記ゆれを考慮できていない

現状、表記ゆれを考慮していません。例として、「1000」と「千」などを同じにすべきなのか、別のものと考えるのかも困難な課題です。どちらも数値であれば同じものとしても良さそうですし、漢字の方が名前の一部であれば別のものと考えるべきです。

記事のタイトルしか回答候補に挙がらない

例えば、「1/10」という記事はありますので、「1割はいくら」という質問には正解できる可能性がありますが、「8%」というタイトルの記事はありませんから、「元号が令和に変わったときの消費税率はいくら?」という質問に正解できることはありません。

次にやること

上記の課題点を考慮し、別のアルゴリズムでの実験をしてみます。

質問応答システムの実装と考察:全体の流れ

質問応答システムの実装と考察についてシリーズ化しているので、目次として各記事をリスト化しています。

その他

更新中

質問応答システムの実装と考察:質問の用意

質問応答システムの動作確認をするには質問を用意する必要があります。

ファクトイド質問応答の場合、一般名詞もありますが、人名などの固有名詞(表現)に始まる語が求められると思います。

そういった固有表現には、既存の分類があるようなので、それらについて質問を作りました。

IREXの固有表現

次のものがあります。

  • 人名 (PERSON)
  • 地名 (LOCATION)
  • 組織名 (ORGANIZATION)
  • 日付 (DATE)
  • 時刻 (TIME)
  • 金額 (MONEY)
  • 割合 (PERCENT)
  • 固有物質 (ARTIFACT)

質問の作成

上のIREXの固有表現に関して、それぞれの種類に属する質問を1つずつ作成しました。

[
    {
        "question": "戦国時代の武将であり、本能寺で織田信長を討ったのは誰?",
        "answer": "明智光秀"
    },
    {
        "question": "政令指定都市を持つ、岡山県の西側に隣接する県はどこ?",
        "answer": "広島県"
    },
    {
        "question": "厚生省と労働省が統合してできた、国民生活の保障と経済の発展を目指す組織は何?",
        "answer": "厚生労働省"
    },
    {
        "question": "昭和46年は西暦何年?",
        "answer": "1971年"
    },
    {
        "question": "朝、昼、夜のうち、日没後の時間であるものはどれ?",
        "answer": ""
    },
    {
        "question": "現在、日本で流通している硬貨で、最も高価なものは何円硬貨?",
        "answer": "500円"
    },
    {
        "question": "1割を既約分数で表すと何分の何?",
        "answer": "1/10"
    },
    {
        "question": "任天堂が2017年3月3日に発売した、据置でも携帯でも使えるゲーム機は何?",
        "answer": "Nintendo Switch"
    }
]

実装する質問システムは、上の question を与え、対応する answer を期待するシステムになります。