ITエンジニアのブログ

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

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

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

最初の記事は次のものになります。こちらから順を追えます。
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

統計学とプログラミング:平均と分散

平均と分散、さらに代表値の例と標準偏差について書きます。

環境

ライブラリ

Pythonではnumpyを使います。

import numpy as np

C言語ではmath.hを使います。

#include <math.h>

データ

テストの得点を想定して、numpyで0~100までの乱数を用意します。

n個の値をx_k (1 \leq k \leq n)とおきます。

# 乱数初期化
np.random.seed(0)
# scoresに0~100までの整数100個を格納
scores = np.random.randint(0, 101, 100)

中身を確認します。

scores
# 出力
array([ 44,  47,  64,  67,  67,   9,  83,  21,  36,  87,  70,  88,  88,
        12,  58,  65,  39,  87,  46,  88,  81,  37,  25,  77,  72,   9,
        20,  80,  69,  79,  47,  64,  82,  99,  88,  49,  29,  19,  19,
        14,  39,  32,  65,   9,  57,  32,  31,  74,  23,  35,  75,  55,
        28,  34,   0,   0,  36,  53,   5,  38,  17,  79,   4,  42,  58,
        31,   1,  65,  41,  57,  35,  11,  46,  82,  91,   0,  14,  99,
        53,  12,  42,  84,  75,  68,   6,  68,  47,   3,  76, 100,  52,
        78,  15,  20,  99,  58,  23,  79,  13,  85])

平均

算術平均 (mean)

よく\muと書かれます。

\mu = \displaystyle \frac{1}{n} \sum_{k=1}^{n} x_k

Python

meanメソッドを使います。

scores.mean()
# 出力
48.75

C言語

配列の中を全て足し、個数で割り算します。

// count: 要素の個数
// array: 得点の配列
double mean(int count, int* array){
    double sum = 0;
    // 各値について
    for(int i = 0; i < count; i++){
        // sumに足し算する
        sum += (double)array[i];
    }
    // 個数で割り算する
    return sum / (double)count;
}

中央値 (median)

中央値は平均値と違い、外れ値(集団と大きく離れた値)の影響を受けにくいです。

例えば、年収の分布であれば、大金持ちが少数含まれていることが想定できる場合、平均値だけでなく中央値も確認したいところです。

np.median(scores)
# 出力
47.0

分散 (variance)

よく\sigma^2と表される分散は、次の式で求められます。

\sigma^2 = \displaystyle \frac{1}{n} \sum_{k=1}^{n} (x_k - \mu)^2

Python

varメソッドを使います。

scores.var()
# 出力
823.4675

C言語

各値に対し、平均との差分を2乗して合計し、個数で割り算します。

// count: 要素の個数
// array: 得点の配列
double variance(int count, int* array){
    // 平均を求める
    double x_mean = mean(count, array);
    double sum = 0;
    // 各値についてsumに足し算していく
    for(int i = 0; i < count; i++){
        // 平均との差分を求める
        double x_diff = (double)array[i] - x_mean;
        // 2乗する
        sum += x_diff * x_diff;
    }
    // 個数で割り算する
    return sum / (double)count;
}

標準偏差

標準偏差Sは、分散の平方根を取れば求められます。

S = \sqrt{\sigma^2}

Python

scores.std()
# 出力
28.696123431571728

C言語

// count: 要素の個数
// array: 得点の配列
double standardDeviation(int count, int* array){
    // 分散の平方根を計算
    return sqrt(variance(count, array));
}

偏差値得点

平均を50とするもので、よく学力試験でよく見られるものですが、今まで求めた平均と標準偏差を用いて、偏差値得点p

p = 10 \cdot \displaystyle\frac{x - \mu}{S} + 50

とします。標準偏差が約28.7だったので、平均48.75から28.7離れると、偏差値得点が10変わることになります。

10 * (scores - scores.mean()) / scores.std() + 50
# 出力
array([48.34472415, 49.39016153, 55.31430666, 56.35974404, 56.35974404,
       36.14795476, 61.93541005, 40.32970427, 45.55689115, 63.32932655,
       57.40518142, 63.67780568, 63.67780568, 37.19339214, 53.22343191,
       55.66278579, 46.60232853, 63.32932655, 49.0416824 , 63.67780568,
       61.2384518 , 45.90537027, 41.72362077, 59.8445353 , 58.10213967,
       36.14795476, 39.98122514, 60.88997267, 57.05670229, 60.54149355,
       49.39016153, 55.31430666, 61.58693092, 67.51107606, 63.67780568,
       50.08711978, 43.11753727, 39.63274602, 39.63274602, 37.89035039,
       46.60232853, 44.16297465, 55.66278579, 36.14795476, 52.87495279,
       44.16297465, 43.81449552, 58.79909792, 41.02666252, 45.20841202,
       59.14757705, 52.17799453, 42.76905814, 44.8599329 , 33.01164263,
       33.01164263, 45.55689115, 51.48103628, 34.75403826, 46.2538494 ,
       38.93578776, 60.54149355, 34.40555913, 47.6477659 , 53.22343191,
       43.81449552, 33.36012176, 55.66278579, 47.29928678, 52.87495279,
       45.20841202, 36.84491301, 49.0416824 , 61.58693092, 64.72324305,
       33.01164263, 37.89035039, 67.51107606, 51.48103628, 37.19339214,
       47.6477659 , 62.28388918, 59.14757705, 56.70822317, 35.10251738,
       56.70822317, 49.39016153, 34.05708001, 59.49605617, 67.85955518,
       51.13255716, 60.19301442, 38.23882951, 39.98122514, 67.51107606,
       53.22343191, 41.02666252, 60.54149355, 37.54187126, 62.6323683 ])

参考文献

統計学入門」(東京大学出版会

統計学とプログラミング:1次元データの可視化

ヒストグラムを作成します。

環境

ヒストグラムの作成

ヒストグラム(棒グラフ)を作成します。

まず、必要なライブラリを読み込みます。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

scikit learn にサンプルデータがあるので、それを使います。今回はwineのデータを読み込みます。

from sklearn.datasets import load_wine
wine = load_wine()

pandasのDataFrameに変換します。

wine_df = pd.DataFrame(wine.data, columns=wine.feature_names)

中味を見てみます。

wine_df.head()

alcoholというカラムがアルコール度数のことでしょう。これをヒストグラムで表示します。

rwidthを1から減らして、棒グラフに隙間ができるようにして表示しました。

plt.hist(wine_df["alcohol"], rwidth=0.9)

f:id:tfull:20190424122122p:plain
wine - alcohol

Ubuntuで行う自己流の環境設定集

Virtual Box や、最近はやっていないですがOSをブートするときにUbuntuを選択することが多く、いくつも設定してきたため、いつもやる操作、近頃の 18.04 LTS の例で挙げます。

適宜、更新する可能性があります。

shell$ # 適当なディレクトリで実行
shell ~/dir$ # ~/dir 下で実行

名前変更

Ubuntuを日本語でインストールすると、ディレクトリが日本語になっているため、ターミナルで操作するときに煩わしい。これを回避するために、ディレクトリ名を英語にします。

shell$ LANG=C xdg-user-dirs-gtk-update

確認画面が出てくるので良しなに進める。

ブラウザ

Firefox が最初から使えますが、 Chrome をよく使っているので、入れます。

  1. Google Chrome で検索し、ダウンロード。
  2. Ubuntu ソフトウェアが表示されるので、インストールボタンを押下。

サーバー化

sshでアクセスできるようにする。

shell$ sudo apt install openssh-server

sshの設定

GitHubレンタルサーバーなどへのアクセスに使用。

shell ~$ mkdir .ssh
shell ~$ chmod 700 .ssh

id_rsassh-keygen か既にあるものを用意。

shell ~/.ssh$ chmod 600 id_rsa
shell ~/.ssh$ chmod 600 authorized_keys # あれば

各種ソフトウェア

apt update

新しいパッケージを入れるため。aptをアップデート。

shell$ sudo apt update

その他必要なソフトウェア

shell$ sudo apt install gcc make zlib1g-dev libssl-dev libffi-dev libbz2-dev libreadline-dev libsqlite3-dev

Git

shell$ sudo apt install git

Pythonとpyenv

まずpyenvをインストール。以下リンクから手順に従う。
GitHub - pyenv/pyenv: Simple Python version management

新しいバージョンのpythonをpyenvを使って入れる。

shell$ pyenv install 3.7.3
shell$ pyenv global 3.7.3

AppleのMagic Keyboard, Magic Mouse 2, Magic Trackpad(黒)の使い心地

f:id:tfull:20190326003103j:plain
Macの入力デバイス

主に iMac で使っていたワイヤレスキーボードを交換したあとの記録です。

今まで、テンキーなしのワイヤレスキーボード、マウス、トラックパッドについて、いずれも電池式のものを使っていました。いくつかの欠点のため、数ヶ月前に交換して使っていましたが、結構気に入っています。

前デバイスの欠点

キーボード (Wireless Keyboard)

  • 表面がゴワゴワしている。

マウス (Magic Mouse)

  • 裏側の電池部分のカバーが取れやすい。

トラックパッド (Magic Trackpad)

  • 特になし。

これに加えて、電池式なので、電池がないときに切れると困る、残量が少ないときにたまに切断するという短所があります。

購入

いずれも黒です。キーボードとトラックパッドが15000円ほど、マウスが10000円ほどでした。黒は2000円ほど高かったので悩みましたが、見た目が気に入ってこちらにしました。

不要なものを売却

  • Wireless Keyboard 1400円 * 2
  • Magic Mouse 1000円
  • Magic Trackpad 1400円

合計5200円。ビックカメラで買い取ってもらいました。

購入品の長所

Magic Keyboard

  • 押し心地が丁度よい。

Magic Mouse 2

  • 変化なし。

Magic Trackpad 2

  • クリックが静電式になった。気持ちいい。

購入品の欠点

Magic Keyboard

  • 特になし。

Magic Mouse 2

  • 充電ケーブルを指す部分が裏にあるので、充電中にマウスを使えない。

Magic Trackpad 2

  • 特になし。

その他、全部に共通していますが、充電が結構もち、頻繁に充電する必要がないです。おそらく1ヶ月くらい充電無しで使えてます。電池を管理しないで良いのがとてもよく、夜にコンビニに電池を買いに行かなくて済むようになりました。

気になる点は、充電ケーブルがLightningであるということです。おそらくこれからUSB Type-Cが主流になると読んでいるので、ケーブルが壊れたときにいつまで入手できるのかが問題です。

雑感

色違いというだけで各デバイス2000円も高いのがやはり納得いかないですが、買ってよかったと思います。

ポインティングデバイスMagic Mouse 2 と Magic Trackpad 2 を併用しています。 iMac 27inch で使っていますが、画面移動などのメインではマウスを使っています。MacBook系(ノートPC)ではトラックパッドがとても好きで、マウスは使わないのですが、iMacのようなデスクトップだとマウスが使いやすいです。iMacでは、トラックパッドはスクロールを沢山するような箇所でマウスより使いやすく、重宝しています。正直なところ、どちらかだけでも十分かもしれません。