ITエンジニアのブログ

IT企業でエンジニアやってる人間の日常について

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 を期待するシステムになります。

質問応答システムの実装と考察:学習用の文章データを解析する

質問応答システムを実装した過程を書き留めています。

最初の記事は次のものになります。こちらから順を追えます。
GitHubのリンクも載せているのでコードを参照できます。

tfull.hatenablog.jp

ファイルからDBに記事を読み込ませる

Wikipediaのダンプファイルを取得し、DBにタイトルと記事の内容のみを取り出して挿入しました。

ダンプファイルはXMLファイルで、xml.etree.ElementTree を使うとパースできます。

Wikipediaの全データを読み込むにはメモリが足りなかったので、1万件ごとに分割したXMLファイルを順に読み込んでいっています。

import xml.etree.ElementTree

def load_xml(path):
    with open(path, "r") as f:
        parser = xml.etree.ElementTree.XMLParser()

        for line in f:
            parser.feed(line)

        return parser.close()

Wikipediaをパースして、中のデータにアクセスします。トップのタグ(xml)は何の名前でも良いのですが、次のような形式になっています。

<xml>
    <page>
        <title>広島県</title>
        <revision>
            <text>広島県は、瀬戸内海に面する件である。</text>
        </revision>
    </page>
    <page>
        <title>...</title>
        <redirect title="..." />
    </page>
</xml>

pageという項目が並んでいて、中に記事のtitleと、文章あるいはリダイレクション(別のページに飛ばす設定)が存在します。

# path: ファイルのパス

# まずXMLをパースする
wikipedia = load_xml(path)

# 各ページに分割してリストにする。
pages = wikipedia.findall("page"):

# 各ページに対して
for page in pages:
    # タイトルへアクセス
    title = page.find("title").text
    # リダイレクションへアクセス(なければ None)
    redirect = page.find("redirect")
    if redirect is not None:
        redirect = redirect.text
    # テキスト(リダイレクトがない場合)
    revision = page.find("revision")
    text = revision.find("text").text

リダイレクトに関しては、使うことがありそうなので、記事とは別のテーブルに保存します。

Wikipedia記法の記事から、平の文章を抽出する。

さて、記事の文字列は取得できたのですが、今の段階では、別記事へのリンクや、テキストの装飾など、Wiki形式の特殊な記法が混じっているので、それからプレーンな文章を抽出する必要があります。

別の方のプログラムで、 Wikipedia Extractor というものがあります。

GitHub - attardi/wikiextractor: A tool for extracting plain text from Wikipedia dumps

上記、試しに使ってみましたが、今回の目的に対しては自分自身で自由に実装したかったのと、昔にも抽出プログラムを記述したことがあったので、自前で実装することにしました。

私が GitHub に上げているコードは常に改良していくつもりですが、そのなかで、テキスト抽出に関するいくつか例を提示します。

厳密にやるのであれば構文解析(tokenizer, parser)を使うべきですが、それだけでとてつもない時間がかかるため、暫定的に正規表現で文字列の置換を行う方法を実行します。

# 正規表現ライブラリ
import re

強調の排除

例:文字が'''強調'''されています。 → 文字が強調されています。

ダッシュ記号が2つ以上連続している部分を単純に消します。

RE_PRIME = re.compile(r"\'{2,}")
text = re.sub(RE_PRIME, "", text)

付帯情報の除去

例:人物名{{誕生|...年,出身|...}} → 人物名
画像の情報など、文章になっていないものを除去します。

RE_BRACKET = re.compile(r"\{[^\{]*?\}") # {から}まで、途中に{を含まない
for i in range(5):
    text = re.sub(RE_BRACKET, "", text)

入れ子になっている場合 {{... {{ ... }} ... }} もあるので、最も内側のものから反応し、複数回繰り返して全部除去するようにしています。

こちらに関しては、現在は除去という対処法をしていますが、重要な情報もよく含まれていることが多いように思いましたので、以降の実装では適宜抽出することになりそうです。

リンクから語句の抽出

例:[[五百円硬貨|500円]] → 五百円硬貨

正規表現だけおいておきます。

RE_LINK_I     = re.compile(r"\[\[[^\[]*?\]\]")
RE_LINK_S = re.compile(r"\[\[(.*?)\|(.*?)\]\]")

おそらく画像系で、[[...|right|350px|thumb| [[ ... ]] ]] みたいな記法を発見したので、こちらは改良の余地があります。

平の文章もDBに保存

これで、構文解析済みの文章データが生成されたので、DBに保存できます。次のようにデータを保存しました。

  • entries:titleとWikipedia記法の文章を入れておく
  • redirections:語句とentriesのidを対応させる。その語句はentries.idを持つentryへリンクされる。
  • plain_texts:entries.idと解析済み文章を保存する。


面倒かつ大事なデータの前処理が終了しました。

これで、文章を学習させたりすることができます。

質問応答システムの実装と考察:構想からデータの用意まで

趣味で質問応答システムを作っています。

構想から構築までの道筋を書いておくことで、今後なにかの役に立つかもしれないと思うため、履歴として残しておきます。

構想

まずは一問一答で単語を回答する、ファクトイド質問形式で正答率を上げることを目標とします。

それの進捗具合に依りますが、ノンファクトイドや対話といったシステムに拡張していければと思います。

環境

主に次の環境で作業を行っています。

  • CentOS7
  • 2.5 GHz 4 Core CPU
  • 4 GB Memory
  • 400 GB Storage

リポジトリ

GitHubに上げています。

github.com

言語

当面は日本語で行こうと思っています。英語にも拡張できたらしたいので、一部の共通化を意識しながら実装していきます。

コーパス

入手しやすいコーパスとしてはWikipediaがあります。

Wikipedia:データベースダウンロード - Wikipedia

定期的に記事のデータがバックアップとしてxmlファイルに保存されています。

wget ダンプファイルのURL

私は現在2019-06-01のデータを使っています。

データの前処理

XMLファイルの分割

展開したXMLファイルはファイルサイズが大きいため、いくつかのXMLファイルに分割しました。
というタグとその閉じタグがあるので、それをカウントしながら、特定の個数(10000)になると、前後にというタグを付けて一つのファイルに保存するという操作を繰り返しました。

238ファイルできたので、237~238万記事くらいあるということになります。

DBへの保存

全データをメモリに蓄えられないことから、DBへの保存をするようにしました。キーを付けておけばタイトルで検索をすることもできます。

文字コード関連でテーブルに付けた情報は defalut charset utf8 collate utf8_bin で、utf8外の文字はロード前に排除しました。

本文の存在する記事と、リダイレクションのタイトルが混在しているため、それは別々のテーブルに保存しています。

その他

ディレクトリや環境変数名で使うための名前が必要だったのですが、Erica(エリカ)という花の名前を用いました。
初代ポケモンのジムリーダーやシャドウバースのリーダーでも好きなキャラなので。

次にやること

DBに保存した記事は、Wikipediaの記法に従って書かれているため、平文を抽出する必要があります。

個人的なお気に入りYouTuberをまとめてみた(2019)

最近、よくYouTubeで動画を観ているのですが、特に私が気に入っているチャンネルを列挙してみようと思います。

特に、

  • 学問系
  • VTuber
  • ゲーム
  • 音楽

のチャンネルを観ています。

AKITOさん

学問系です。

私は、高校生のときは勉強に没頭し、楽しんでいました。特に数学が好きだったのですが、その時の記憶が呼び覚まされて、勉強系の動画をよく観るようになりました。

AKITOさんは数学に関してとても優秀な方で、多くは高校数学や大学数学に関して、学びのある動画を投稿されています。

最初に目に止まったのが「AKITOの特異点」というチャンネルで、他にも「AKITOの暇つぶしチャンネル」と「AKITOの勉強チャンネル」という、全部で3つのチャンネルがあります。

チャンネル1

www.youtube.com

チャンネル2

www.youtube.com

予備校のノリで学ぶ「大学の数学・物理」(たくみさん)

大学で学ぶ数学、物理、化学などの解説に加え、大学受験の内容も扱われています。

私は、かつて理系大学生だったのですが、大学1,2年のときは学問の背景も知らないままにただ数式を弄っていたような感覚だったので、たくみさんのチャンネルで背景や意味などを補うことができ、とても勉強になりました。

最近は、毎週積分の問題を観ています。

チャンネル

www.youtube.com

鈴木貫太郎さん

毎朝、大学入試問題を始めとする数学の問題を投稿されています。

難易度も丁度良いことが多く、毎日とは限りませんが、サムネイルで解法を考えた後、動画を観るのが私の定番となっています。

チャンネル

www.youtube.com

.LIVE

次はVTuberです。

私は、VTuberは四天王と呼ばれる4(+1)人の動画をよく観ていましたが、最近は.LIVE(どっとライブ)にハマっています。

12人の個性的なメンバーが集まっており、それぞれのチャンネルで、頻繁にライブが配信されています。

可愛い見た目と裏腹に、やっているゲームの多くがやたらと硬派だという印象があります。(FPSやホラーゲームなど)

私が特に推しているのがヤマトイオリさん(イオリン)で、プレイするゲームが比較的温和なのと、喋り方もとても優しい感じです。

おすすめ動画

www.youtube.com

Quick, Draw!というゲームで、英語に翻弄されるイオリン。

www.youtube.com

イクラで逃走中。
ある程度メンバーのことを知っておいたほうが楽しめそうです。

www.youtube.com

学力テスト(前編)(後編もあります。)

.LIVEのチャンネル

www.youtube.com

つるおか(かものはし)さん

次はゲーム系です。私は、スマホで遊べるカードゲームであるシャドウバースが好きで、いろいろな配信者の動画を観ています。

つるおか(かものはし)さんの動画はもともとニコニコ動画で観ていたのですが、今はYouTubeにも投稿されています。

脊髄反射で出される喋りと、カードの独特な呼び方(万人に向けた歌なんて女、獲物発見行くぞの男など)にハマっています。

ドラゴンクラスをメインで使われていまして、私もドラゴンクラスが好きなのも要因かもしれません。

おすすめサムネイル

www.youtube.com

むじょっくすTV

主にシャドウバースの攻略動画を投稿されています。はしゃいでいるむじょるさんとストッパー役のくすきさんがとても良い相性で、観ていて飽きません。

たまにシャドウバース以外の動画も投稿されていて、ラジオ風動画もあります。

www.youtube.com

マグロヘッドさん

シャドウバースで一風変わったデッキを良く使われています。以前ニコニコ動画でよく観ていました。

生放送ではくだけますが、喋り方が丁寧で聴きやすいです。

とても楽しそうにゲームをプレイされるのも良いです。

www.youtube.com

けそポテトさんとはねむーんさん

シャドウバースで特別対戦をされています。

ルールのバリエーションが豊富で飽きませんし、冒頭に入るルール導入の茶番もセンスがあります。

プレイ中によく奇跡が起こったりします。(体感)

おすすめ動画

www.youtube.com

陽光モルディカイ(の上を行く・・・)

チャンネル

www.youtube.com

がっとれーさん

2018分から任天堂E3から反応動画を観るようになり、様々な投稿者の動画を観るようになりました。

がっとれーさんはかなり知識が豊富で、細かいことも全部拾ってくれます。

チャンネル

www.youtube.com

トメイトウさん

同じく反応動画を投稿されている方ですが、テンションの高さが甚だしく、楽しませていただいています。

おすすめ動画

www.youtube.com

大乱闘スマッシュブラザーズSPECIALの初登場の反応動画 (※ 音量注意)

チャンネル

www.youtube.com

Avicii さん

次は音楽です。EDMで検索して発見した "Wake Me Up" を気に入り、 Aviciiさんの音楽をよく聴いています。とてもキャッチーなメロディーやモチーフが耳に響きます。

残念ながら Avicii さんは2018年に亡くなってしまいました。新曲を聴けることはもう無いですが、彼の曲をこれからも聴き、ときには思いを馳せたいと思います。

チャンネル

www.youtube.com

Jonas Blue さん

同じくEDMで検索して知ったのですが、"Rise" の独特なフレーズが耳から離れません。

チャンネル

www.youtube.com

米津玄師さん

有名すぎて語ることは無いくらいですが、私はボカロP時代のときからずっと好きで聴いています。

彼の影響で私も音楽を作るようになりました。

チャンネル

www.youtube.com

QuizKnock

最後はエンタメです。

TV番組の東大王でもおなじみのクイズ王、伊沢さんを始めとし、高学歴かつ知識の豊富なメンバーが集まります。

クイズという明確なテーマと、卓越した企画力で惹きつけられます。

よく練られた面白いルールのクイズで飽きずに観られ、頭を使いますし、知識を深めることもできます。

おすすめを挙げておくので、何も言わずに観てください。

おすすめ

www.youtube.com

4000択クイズ

www.youtube.com

東大主(東大王のパロディ)

チャンネル

www.youtube.com

おまけ

私のチャンネルです。音楽を作っています。自身が辿ってきた道がここにあります。

www.youtube.com