株式会社ホクソエムのブログ

R, Python, データ分析, 機械学習

darts-cloneを使って最長一致法で分かち書きしてみる

ホクソエムサポーターの白井です。

呪術廻戦をみて喜久福が食べたくなりました *1

今回は形態素解析について深堀りしてみます。

日本語の自然言語処理において、形態素解析は必ずといっていいほど通る道です。 形態素解析を必要としないSentencePieceのような深層学習向けのtokenizerも出現していますが、品詞単位で分割する形態素解析が重要であることは変わりありません。

そんなこんなで、『実践・自然言語処理シリーズ2 形態素解析の理論と実装』 (以降「形態素解析本」と表記)を読んでいます。 リンク先の目次を見て分かるとおり、基礎の部分から実装まで説明されている本です。

今回は4章で紹介されている darts-clone を使って、精度は粗いが高速で分かち書きができる最長一致法で、どれぐらい分かち書きが可能かを検証します。

事前知識・辞書引き

辞書を使って分かち書きする場合、単語の検索時間が課題となります。 単語を検索するのではなく、部分文字列から辞書に含まれる単語を探索するためです。

すべての開始位置と終了位置を探索し、辞書に含まれるかどうか判定する場合、計算時間がO(n2)かかります。 (実際はハッシュ値を計算するのに文字列長に依存したコストがかかるため、O(n3)です)

一般的に、効率的な単語の探索には 共通接頭辞検索 (Common Prefix Search) による辞書引きが用いられます。 これは文字列を前から探索し、前方の部分文字列と一致する単語を探す方法です。

共通接頭辞検索には トライ (Trie) と呼ばれるデータ構造を用います。 トライは木構造で、文字を1つのノードとし、遷移することで文字列を表現します。

Trie example

Booyabazooka (based on PNG image by Deco). Modifications by Superm401., Public domain, via Wikimedia Commons

ダブル配列 はトライの実装方法のひとつであり、dartsdarts-clone はその実装ライブラリです。 ダブル配列についての詳しい説明は形態素解析本や darts-cloneに記載の参考文献 を参照してください。

darts-cloneを使ってみる

実際にdarts-cloneを動かしてみます。

単語辞書を準備し、darts-clone形式で構築することを目指します。

単語辞書

辞書はmecab用の辞書であるIPA辞書を使います。 Mecabのページからダウンロードし、インストールします。

デフォルトの文字コードは enc-jp ですが、今回は扱いやすいようにutf-8 に変換します。 darts-cloneで辞書を作成するだけであれば、品詞などの情報が不要なので、単語だけ切り取り、ソートしておきます。

$ iconv -f EUC-JP /path/to/mecab-ipadic/Noun.csv|cut -d"," -f1 |LC_ALL=C sort|uniq > /output/path/Noun.csv

iconvを使って文字コードを変換していますが、configureで文字コードを再構築することもできるようです。(文字コード変更)

また、日本語をソートする場合、sortの前に LC_ALL=C のオプションが必要なので注意しましょう。正しくソートされていないと辞書を構築する際エラーになります。

ちなみに複数ファイルをまとめる場合、awkを使ってiconvをかませました。

$ ls Noun.* | awk '{print "iconv -f EUC-JP " $1}'|sh |cut -d"," -f1 |LC_ALL=C sort|uniq >> /output/path/Noun_all.csv

darts-clone

darts-clonegit clone しインストールします。configure ファイルがないので、autoreconfconfigure ファイルを作成します。 このとき、autoreconf automake パッケージをあらかじめインストールしておく必要があります。

Mac OSで試しているため、brew でインストールしていますが、それぞれのOSに合わせて darts-clone をインストールしてください。

$ brew install autoreconf automake
$ autoreconf -i
$ ./configure
$ make

辞書をdarts-clone形式に変換

darts-cloneには、単語一覧をDoubleArrayFile に変換するプログラム mkdarts が付属しているので、それを用います。

$ ./src/mkdarts /path/to/Noun.csv /path/to/ipadic_dict/Noun.dic

keys: 58793
total: 511631
Making double-array: 100% |*******************************************|
size: 275968
total_size: 1103872

名詞だけを変換し、Noun.dic を構築しました。

実際に動くか確認しましょう。 darts を実行すると共通接頭辞検索がインタラクティブに実行できます。

$ ./src/darts /path/to/ipadic_dict/Noun.dic

もも
もも: found, num = 1 3822:6
すもも
すもも: found, num = 2 1996:3 2072:9
ほげ
ほげ: not found

出力ではマッチした要素の個数 (num = 1) と、単語keyに対応するvalueと文字長 (3822:6) が返ってきます。 文字長が単語の長さと一致しないのはバイト文字で表されているためです。 日本語で使う文字の1文字はだいたい3バイトなので、「もも」の結果にある文字長6は2文字 (=もも)にマッチしたという意味になります。

(もちろん3バイトではない文字もあります。UTF-8の文字コード表 - 備忘帳 - オレンジ工房などで一覧で見られるので参考にしてください。)

共通接頭辞の検索では、複数の単語と一致する場合もあります。 例えば、下の実行結果をみると「すもも」の場合、「す」と「すもも」の2つにマッチしているのがわかります。

$ ./src/darts /path/to/ipadic_dict/Noun.dic

す
す: found, num = 1 1996:3
すも
すも: found, num = 1 1996:3
すもも
すもも: found, num = 2 1996:3 2072:9

最長一致法の実装

darts-cloneを使って、最長一致で分かち書きするプログラムを実装します。 最長一致法は最初の文字から共通接頭辞検索し、一番 長く 一致した単語を採用する、ルールベースの分かち書きです。

「すもももももももものうち」の場合、以下のように実行します。

$ ./src/darts Noun.dic
すもももももももものうち 
すもももももももものうち: found, num = 2 1996:3 2072:9
# 「す」「すもも」 → 「すもも」
もももももものうち
もももももものうち: found, num = 1 3822:6
# 「もも」 → 「もも」
もももものうち
# 
# 中略
# 
のうち
のうち: not found
# 一致単語なし → 「の」
うち
うち: found, num = 1 358:6
# 「うち」→ 「うち」

ここで注意したいのは以下の2点です。

  • 「す」「すもも」の2つと一致する場合、長い「すもも」を1単語する
  • 「のうち」のように一致する単語がない場合、一番最初の1文字である「の」を1単語とする

結果「すもも/もも/もも/もも/の/うち」と分割できました。 名詞辞書だけの場合、「も」1文字ではなく「もも(桃)」で分割されていますね。

この例だと最長一致法では全然うまくいかないように見えますが、「スモモも桃も桃のうち」のようにカタカナ・漢字を混ぜると正しく分割されそうなことは感覚的にわかるかと思います。

実際、形態素解析本には以下のように述べられています。

単純なアルゴリズムにもかかわらず,90%以上の分割精度が得られるため,大規模なテキスト集合から大ざっぱな単語頻度を高速に求める処理に向いています.

実践・自然言語処理シリーズ2 形態素解析の理論と実装 P76

では、どれぐらいの精度で分割可能なのか確かめてみます。

python実装

ここからはdarts-cloneのpythonバインディングを利用し、pythonで実装します。 darts-clone-pythonSudachiPyにも使われているdarts-cloneのpythonバインディングです。

形態素解析本のC++実装とSudachiPyの実装を参考にしつつ実装します。

Python 3.8 で動作確認しています。 darts-clone-pythonは v0.9.0 です。

import sys
import dartsclone


class DartsDict:
    def __init__(self, filename):
        self.trie = self.load_darts(filename)

    @staticmethod
    def load_darts(filename):
        darts = dartsclone.DoubleArray()
        darts.open(filename)
        return darts

    def longest_match(self, line: str):
        line_b = line.encode("utf-8")
        begin_str = 0
        begin = 0
        end = len(line_b)
        while begin < end:
            longest_length = 0
            key = line_b[begin:]
            result = self.trie.common_prefix_search(key, length=len(key))
            for (word_id, length) in result:
                longest_length = max(longest_length, length)

            if longest_length == 0:
                # 一致する単語がなかった場合
                longest_length = len(line[begin_str].encode("utf-8"))

            word = line_b[begin:begin+longest_length].decode()
            yield word

            begin += longest_length
            begin_str += len(word)


def sample():
    darts = DartsDict("/path/to/ipadic_dict/Noun.dic")
    for w in darts.longest_match("すもももももももものうち"):
        print(w)
    print("EOS")


def main(dic_file, text_file):
    darts = DartsDict(dic_file)
    with open(text_file, "r")as f:
        for line in f:
            words = []
            for w in darts.longest_match(line.strip()):
                words.append(w)
            print(" ".join(words))


if __name__ == '__main__':
    args = sys.argv[1:]
    if len(args) < 2:
        print("Usage: python main.py dic_file text_file")
    else:
        main(args[0], args[1])

検証

実装したところで、最長一致法でどこまで正確に分かち書きできるのか検証していきます。

今回は livedoor ニュースコーパス のテキストを使ってmecabの分かち書きと比較していきます。

データ

livedoor ニュースコーパスからダウンロードできるデータ ldcc-20140209.tar.gz の記事から、トピックごとそれぞれ1つずつ、合計9つの文書を使います。

具体的にはディレクトリごとsortして一番上のテキストデータを使いました。 ただし、1~2行目のURL・日付は取り除いてます。

辞書はipadic (mecab-ipadic-2.7.0-20070801) のcsvを使って4種類作成しました。 名詞・動詞を選んだのは単語数が多く、重要度が高いと予想したためです。

名前 説明 単語数
Noun 名詞・一般 (Noun.csv) だけ 58,793
NounAll 全ての名詞 (Noun*.csvで指定) 197,489
Verb 動詞 (Verb.csv)だけ 101,751
NounVerb NounAll + Verb 296,935

mecabのIPA辞書を使った -Owakati の出力結果を正解データとし、正解データと一致したかどうかで評価します。 MevAL単語境界判定のエラー分析 をベースに評価コードを作成しました。

def eval_line(gold, pred):
    gold_cnt, pred_cnt = 0, 0
    idx = 0
    tp, fp, fn = 0, 0, 0
    for g in gold:
        gold_cnt += len(g)
        if g == pred[idx]:
            # print("true ", g, pred[idx])
            tp += 1
            pred_cnt += len(pred[idx])
            idx += 1
        else:
            if gold_cnt < pred_cnt + len(pred[idx]):
                fn += 1
                # print("fn  ", g, pred[idx])
            else:
                tmp = []
                while gold_cnt > pred_cnt:
                    tmp.append(pred[idx])
                    pred_cnt += len(pred[idx])
                    idx += 1
                    fp += 1
                # print("fp  ", g, " ".join(tmp))
    return tp, fp, fn

結果と考察

文書ごとのF値は以下の表のとおりです。 基本的に単語数が多いほどF値は高い結家となりました。

Noun NounAll Verb NounVerb 行数
dokujo-tsushin-4778030.txt 0.593 0.725 0.556 0.766 25
it-life-hack-6292880.txt 0.567 0.680 0.449 0.725 32
kaden-channel-5774093.txt 0.659 0.807 0.574 0.849 19
livedoor-homme-4568088.txt 0.693 0.847 0.565 0.878 18
movie-enter-5840081.txt 0.612 0.702 0.642 0.788 23
peachy-4289213.txt 0.638 0.765 0.534 0.795 16
smax-6507397.txt 0.485 0.536 0.425 0.555 79
sports-watch-4597641.txt 0.614 0.780 0.562 0.841 10
topic-news-5903225.txt 0.517 0.607 0.463 0.658 58

一番F値が良いのNounVerbのprecisionとrecallをみてみるとprecisionが低いことがわかります。 FalsePositive(正解にはない境界があると予測)、つまり正解より細かく分割している数が多いということです。

precision recall F1
dokujo-tsushin-4778030.txt 0.683 0.872 0.766
it-life-hack-6292880.txt 0.585 0.954 0.725
kaden-channel-5774093.txt 0.770 0.946 0.849
livedoor-homme-4568088.txt 0.802 0.970 0.878
movie-enter-5840081.txt 0.695 0.909 0.788
peachy-4289213.txt 0.704 0.912 0.795
smax-6507397.txt 0.398 0.919 0.555
sports-watch-4597641.txt 0.765 0.934 0.841
topic-news-5903225.txt 0.507 0.937 0.658

具体的に分割結果をみてみます。

まず、以下のように名詞、特に漢字が多い文においてはほぼ同じ分割になりました。

今月 8 日 、 都内 ホテル で は 、 総合 格闘 家 ・ 吉田 秀彦 の 引退 試合 興行 「 ASTRA 」 の 開催 が 発表 さ れ た 。

今月 8 日 、 都内 ホテル で は 、 総合 格闘 家 ・ 吉田 秀彦 の 引退 試合 興行 「 A S T R A 」 の 開催 が 発表 され た 。 上がmecab、下がNounVerbの分割 sports-watch-4597641.txt

一方、辞書の問題として、「Twitter」や「TV」のような英単語が「T w i t t e r」「T V」と1文字ごと分割されてしまう課題があります。 これは英単語を辞書に登録することで解消されるはずです。

画面 下 に は Facebook 、 Twitter 、 SHARE ( Facebook 、 Twitter 、 メール 、 SMS ) で の 共有 、

画面 下 に は F a c e b o o k 、 T w i t t e r 、 S H A R E ( F a c e b o o k 、 T w i t t e r 、 メール 、 S M S ) で の 共有 、 上がmecab、下がNounVerbの分割 smax-6507397.txt

しかしながら、「すももも〜」と同様、ひらがなの多い部分については最長一致というルール上、mecabと同じ結果を出力することができないです。

その 過半数 は 毎年 5 , 000 円 程度 かかる 更新 費用 や その 手続き について 不満 を 持っ て いる 。(中略)性能 面 で 劣る の で は という 不安 から 導入 を 控え て いる という 状況 に ある 。

そ の 過半数 は 毎年 5 , 0 0 0 円 程度 かかる 更新 費用 や そ の 手続き につい て 不満 を 持っ てい る 。(中略)性能 面 で 劣る の で はと いう 不安 から 導入 を 控え てい る とい う 状況 に ある 。 上がmecab、下がNounVerbの分割 it-life-hack-6292880.txt

上の例ではNounVerbが「持っている」「控えている」について、「持っ/てい/る」「控え/てい/る」と左に最長であるように分割されます。 ですが、正しい分割は以下のように「て/いる」であり、「いる」が動詞であるように分割したい部分です。

持っ    動詞,自立,*,*,五段・タ行,連用タ接続,持つ,モッ,モッ
て      助詞,接続助詞,*,*,*,*,て,テ,テ
いる    動詞,非自立,*,*,一段,基本形,いる,イル,イル

おわりに

最長一致法を実装しながら、ダブル配列ライブラリdarts-cloneの使い方を解説しました。

ナイーブな最長一致法は高速かつ、ある程度は使える実装ですが、分割には課題点もあります。

mecabの実装のように、最適化アルゴリズムを使うべきだというお気持ちがちょっと分かった気がします。

また、今回は細かい部分は割愛して darts.open(filename) で辞書を読み込みましたが、形態素解析本では全部をメモリに載せず メモリマップトファイル (pythonでは mmap) を使って最適化する実装が提案されています。

実際、IPA辞書の全単語でdarts-cloneの辞書を構築してメモリに乗せようとするとsegmatation faultします……。 この記事はあくまで「使ってみた」系記事ですので、ご了承ください。

参考資料

mecabとdarts関連

おまけ資料:SudachiPyで最長一致法

前述のように、SudachiPyはdarts-cloneで辞書引きしているので、実装されたクラスをうまく活用すれば最長一致法での分かち書きが実装できます。

具体的には BinaryDictionary クラスで辞書の設定をしているので、そこから単語辞書 (DoubleArrayLexiconのインスタンス)だけを抜き出して使います。

ちなみにDoubleArrayLexiconでは mmap を使った辞書の読み込みを行ってます。

from pathlib import Path
from sudachipy.dictionarylib.binarydictionary import BinaryDictionary

# 辞書のパス
system_dic = (Path(import_module('sudachidict_core').__file__).parent / 'resources' / 'system.dic') 

dict_ = BinaryDictionary.from_system_dictionary(system_dic)
lexicon = dict_.lexicon


def longest_match(line: str):
  line_b = line.encode("utf-8")
  begin_str = 0
  begin = 0
  end = len(line_b)
  while begin < end:
    longest_length = 0
    longest_word_id = None
    for (word_id, length) in lexicon.lookup(line_b, begin):
      if longest_length < length:
        longest_length = length
        longest_word_id = word_id


    if longest_length==0:
      # print(line[begin_str])
      longest_length = len(line[begin_str].encode("utf-8"))

    word = line_b[begin:longest_length].decode()
    yield word

    begin = longest_length
    begin_str += len(word)

SudachiPyは v0.4.9 で確認しています。

SudachiPyの辞書であるSudachiDict の語彙数が多いため、結構正確に分割できると思います。 当たり前ですがSudachiPyより高速です。

*1:宮城にしかないと思っていたのですが、ググったら関東にも店舗を出店しているみたいで、正直驚いています。

EDINET APIって知ってる? ~有価証券報告書をもっと楽にダウンロードする話~

はじめに

こんにちは, ホクソエムサポーターのKAZYです。

最近はペンギンに興味があります🐧。

世界最大のペンギンであるコウテイペンギンを日本で見るならば名古屋港水族館 (愛知) かアドベンチャーワールド (和歌山) らしいです。

ところで, 平成31年3月17日からEDINETに提出された書類をAPIで取得できるようになったことをご存知でしょうか?

だからなんなの?っていう方聞いてください。

もうブラウザポチポチやらなくても有価証券報告書ダウンロードできるんですよっ!!!!

「退屈なことはPythonにやらせよう」マンになる時が来たのです。

今回はEDINET APIで有価証券報告書を保存するための最低限の知識任意の日付の有価証券報告書をダウンロードするPythonプログラムを紹介します。

読んだら幸せになりそうな方

  • 提出日を指定した有価証券報告書のダウンロードしたい方 (e.g. 1年分全部欲しいぜ!!!)
  • ぼんやりEDINET APIで何ができるか知っておきたい方

読んでもあんまり幸せにならない方

  • 企業名を指定した有価証券報告書のダウンロード (e.g. マクドナルドの有価証券報告書が5年分ほしいぜ!!!!)

↓ここで紹介しているぜ↓ blog.hoxo-m.com

  • 全然暇じゃないし, 正確of正確な情報を取りに行きたい
    • こちらのAPIの仕様書に使い方は全て書いてある, Have a nice day👋

どうでもいいからはよXBRLファイルをDLさせろ💢って方

KAZYの拙いスクリプトをどうぞ

poetry使える人

git clone https://github.com/KAZYPinkSaurus/disclosure-crowler.git
cd disclosure-crowler
# ライブラリのインストール
poetry install

#オプションを表示
poetry run python -m disclosure.main --help

# 2020/09/04から2020/10/04の有価証券報告書をダウンロードしてxbrlファイルを抽出
poetry run python -m disclosure.main --from 2020-09-04 --to 2020-10-04 -x
ls output/*

poetry使えない人

git clone https://github.com/KAZYPinkSaurus/disclosure-crowler.git
cd disclosure-crowler
pip install requests==2.24.0 python-dateutil==2.8.1 loguru==0.5.3 click==7.1.2

#オプションを表示
poetry run python -m disclosure.main --help
# 2020/09/04から2020/10/04の有価証券報告書をダウンロードしてxbrlファイルを抽出
python -m disclosure.main --from 2020-09-04 --to 2020-10-04 -x

KAZY< ばいばい 👋

🤖 < ありがとうございました

APIって...?

According to Wikipedia, APIとは

アプリケーションプログラミングインタフェース(API、英: Application Programming Interface)とは、広義ではソフトウェアコンポーネント同士が互いに情報をやりとりするのに使用するインタフェースの仕様である。 前述のとおりAPIは各種システム/サービスがそのシステム/サービスを利用するアプリケーションに対して公開するインタフェースである。

ということのようです。 インターフェースと仕様提供するから退屈なことはPythonにやらせるんだぞっ!!と言う声が聞こえてくる気がします🦻。

EDINET APIについて

EDINET APIは以下の2つのAPIの総称です。

  1. 提出された書類を把握するためのAPI
    • 書類のメタ情報を教えてくれる
  2. 提出された書類を取得するためのAPI
    • 実際に書類を手に入れられる

取得できる期間

  • EDINET APIで取得できる書類は直近5年分です。*1

特に注意してほしいこと

利用規約の第5条(禁止事項)には

1.利用者は、以下に掲げる行為を行ってはならないものとします。
(1) 本機能の健全な運営を害する一切の行為
(2) 短時間における大量のアクセスその他の本機能の運用に支障を与える行為

とあります。

(2)の短時間の大量アクセスは起こしやすいので特に注意しましょう。

とはどういう意味合いなのでしょう。まったくわかりません。

〜〜〜以下、KAZYの脳内のやり取り〜〜〜

KAZY < どれくらいアクセスしたら大量アクセスなの?

EDINET API< う~ん, とにかく運用に支障を与えるくらいのアクセスはやめてくれってことや!

KAZY.o0(運用次第やな....)

EDINET API< 無限ループするスクリプト書いてアクセスし続けるとかやめてくれよな!

KAZY< OK

EDINET API< アクセスするスクリプトをたくさん並列して動かすとかもやめてくれよな!!

KAZY< 理解

有価証券報告書のXBRLファイルをダウンロード(コマンドライン編)

きっと理解しやすい方がいると思うのでコマンドラインからの取得方法を見ていきましょう。

目標

  • 2020/01/07に提出された有価証券報告書を1つ取得する

流れ

  1. 日付をメタファイルを取得
  2. メタファイルから書類ID(docID)を取得
  3. docIDを指定して有価証券報告書をダウンロード

メタファイル取得(2020/01/07)

curl "https://disclosure.edinet-fsa.go.jp/api/v1/documents.json?date=2020-01-07&type=2"

↓レスポンス

{
    "metadata":
        {
            "title": "提出された書類を把握するためのAPI",
            "parameter":
                {
                    "date": "2020-01-07",
                    "type": "2"
                },
            "resultset":
                {
                    "count": 179
                },
            "processDateTime": "2020-10-04 00:00",
            "status": "200",
            "message": "OK"
        },
    "results": [
        {
            "seqNumber": 1,
            "docID": "S100HNA6",
            "edinetCode": "E08957",
            "secCode": null,
            "JCN": "4010401049128",
            "filerName": "三井住友DSアセットマネジメント株式会社",
            "fundCode": "G12668",
            "ordinanceCode": "030",
            "formCode": "07A000",
            "docTypeCode": "120",
            "periodStart": "2018-10-11",
            "periodEnd": "2019-10-10",
            "submitDateTime": "2020-01-07 09:01",
            "docDescription": "有価証券報告書(内国投資信託受益証券)-第2期(平成30年10月11日-令和1年10月10日)",
            "issuerEdinetCode": null,
            "subjectEdinetCode": null,
            "subsidiaryEdinetCode": null,
            "currentReportReason": null,
            "parentDocID": null,
            "opeDateTime": null,
            "withdrawalStatus": "0",
            "docInfoEditStatus": "0",
            "disclosureStatus": "0",
            "xbrlFlag": "1",
            "pdfFlag": "1",
            "attachDocFlag": "1",
            "englishDocFlag": "0"
        },
        {
            "seqNumber": 2,
            "docID": "S100HOID",
            ︙続く

メタファイルから書類ID(docID)を取得

文書IDは有価証券報告書のダウンロードのために必要です。

先程のレスポンスをresults->docIDと辿ります。

︙省略
            "docID": "S100HNA6",
︙省略       

一番上のdocIDにはS100HNA6という要素が入っていますね。

これです。

有価証券報告書を取得

XBRLファイルが圧縮されたzipファイルを取得します。

書類IDS100HNA6のファイルをダウンロードしてみましょう。

エンドポイントは https://disclosure.edinet-fsa.go.jp/api/v1/documents/ドキュメントのID って感じで提供されています。

そしてパラメータをtype=1とすると書類が取得できます。*2

ダウンロードしましょう (with curlコマンド)!!!

## -o は標準週力じゃなくてファイルに書き出してくれるオプションだよ
curl -o S100HNA6.zip "https://disclosure.edinet-fsa.go.jp/api/v1/documents/S100HNA6?type=1"
🐦.o0(たーみなる)% curl -o S100HNA6.zip "https://disclosure.edinet-fsa.go.jp/api/v1/documents/S100HNA6?type=1"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  670k  100  670k    0     0   263k      0  0:00:02  0:00:02 --:--:--  262k

🐦.o0(たーみなる)% ls -lh S100HNA6.zip
-rw-r--r--  1 kazy  staff   670K 10 10 11:26 S100HNA6.zip

670KBのファイルがダウンロードされました。 やった!

有価証券報告書のXBRLファイルをダウンロード(Python編)

次にダウンロードするPythonスクリプトを書いてみます。

🤖 < requests 使うだけでしょ? そんなことでブログ膨らますなよ

KAZY< ....

目標(コマンドライン編と同じ)

  • 2020/01/07に提出された有価証券報告書を1つ取得する

流れ(コマンドライン編と同じ)

  1. 日付をメタファイルを取得
  2. メタファイルから書類ID(docID)を取得
  3. docIDを指定して有価証券報告書をダウンロード

メタファイル取得(2020/01/07)

>>> import requests
>>> 日付 = "2020-01-07"
>>> メタファイルのタイプ = 2
>>> META_URL = f"https://disclosure.edinet-fsa.go.jp/api/v1/documents.json?date={日付}&type={メタファイルのタイプ}"
>>> text = requests.get(META_URL).text
>>> print(text)
{
    "metadata":
        {
            "title": "提出された書類を把握するためのAPI",
            "parameter":
                {
                ︙省略

取得できました。

メタファイルから書類ID(docID)を取得

返ってきたjson形式のテキストを辞書として読み込みましょう。

# さっきの続きだよ
>>> import json
>>> meta_dict = json.loads(text)
>>> meta_dict.keys()
dict_keys(['metadata', 'results'])

読み込めてそうですね。 1つ目の書類の情報を出力してみましょう。

>>> meta_dict['results'][0]
{'seqNumber': 1, 'docID': 'S100HNA6', 'edinetCode': 'E08957', 'secCode': None, 'JCN': '4010401049128', 'filerName': '三井住友DSアセットマネジメント株式会社', 'fundCode': 'G12668', 'ordinanceCode': '030', 'formCode': '07A000', 'docTypeCode': '120', 'periodStart': '2018-10-11', 'periodEnd': '2019-10-10', 'submitDateTime': '2020-01-07 09:01', 'docDescription': '有価証券報告書(内国投資信託受益証券)-第2期(平成30年10月11日-令和1年10月10日)', 'issuerEdinetCode': None, 'subjectEdinetCode': None, 'subsidiaryEdinetCode': None, 'currentReportReason': None, 'parentDocID': None, 'opeDateTime': None, 'withdrawalStatus': '0', 'docInfoEditStatus': '0', 'disclosureStatus': '0', 'xbrlFlag': '1', 'pdfFlag': '1', 'attachDocFlag': '1', 'englishDocFlag': '0'}

1つ目の書類のdocIDを出力してみましょう。

>>> 書類のID = meta_dict['results'][0]['docID']
>>> 書類のID
'S100HNA6'
# ついでに名前も表示してみた
>>> meta_dict['results'][0]['filerName']
'三井住友DSアセットマネジメント株式会社'

取得できました。

有価証券報告書を取得

XBRLファイルが圧縮されたzipファイルを取得します。

>>> 書類のタイプ = 1
>>> 書類取得のURL = f"https://disclosure.edinet-fsa.go.jp/api/v1/documents/{書類のID}?type={書類のタイプ}"
>>> response = requests.get(書類取得のURL)
>>> response
<Response [200]>

ステータスコードが成功のレスポンスなので取れてるっぽいですね。

保存してみましょう。

with open(f"{書類のID}_python.zip", 'wb') as f:
    f.write(response.content)
🐦.o0(たーみなる)% ls -lh S100HNA6_python.zip
-rw-r--r--  1 kazy  staff   670K 10 11 13:32 S100HNA6_python.zip

取得できましたね。

パチパチ👏

................さて,

ここまでネット上で説明されまくっている話をほぼそのまま説明しました。

ここからは適当なワークをしてちっとは意味がある記事風に仕上げていきたいと思う。💪💪💪💪💪💪💪💪💪💪💪

(適当なワーク1)有価証券報告書が提出されるのが多いのは何月?

結論

  • 6月
    • なんで?: 3月決算の企業が多い + 有価証券報告書の提出は決算後3ヶ月以内と義務付けられている

検証物語

企業によって有価証券報告書を提出する日付は様々です。

提出が多い月, 少ない月などはあるのでしょうか?

本日紹介したEDINET APIを使って集計してみます。

〜〜〜以下に集計の際にKAZYの脳内で行われたやり取り〜〜〜

KAZY< 魔法使いさん, 集計して!!

🧙‍♂️< えいやっ! ぼんっ(魔法の音)

KAZY< 1年分ないやんけ(2020年10月執筆時)

🧙‍♂️< 2019ね〜ん, えいやっ! ぼぼんっ(魔法の音)

KAZY< もう一声!!!

🧙‍♂️< 2018ね〜ん, えいやっ! ぼぼぼんっ(魔法の音)

KAZY< 6月提出が圧倒的に多いな

🧙‍♂️< 決算月が3月の企業が多いからじゃな

KAZY< 3ヶ月ずれとるやんけ

🤖 < 有価証券報告書は各事業年度終了後、3か月以内の金融庁への提出が義務づけられているんやでぇ

KAZY< なるほど

(適当なワーク2)有価証券報告書が提出されるのが多いのはいつ?

結論

  • 金曜日
    • (なんで?) しらん, 締切になりがちなんちゃう?

      検証物語

以下に集計の際にKAZYの脳内で行われたやり取り。

KAZY< 魔法使いさん, こんどは曜日で集計して!!

KAZY< 3年分頼むわ!!!

🧙‍♂️< そいやっ! ぽんっぽんっぽんっ(魔法の音)

KAZY< 金曜日が多いな

KAZY< 締切に設定されがちなんやろな

KAZY< 土日に提出はしないんだな

(おまけ)期間を指定して有価証券報告書のXBRLファイルをダウンロードして展開してXBRLファイルのみ抽出するしてあとは削除しちゃうプログラムのスクリプト

どぞ。

github.com

↓使い方(再掲)↓

poetry使える人

git clone https://github.com/KAZYPinkSaurus/disclosure-crowler.git
cd disclosure-crowler
# ライブラリのインストール
poetry install

#オプションを表示
poetry run python -m disclosure.main --help

# 2020/09/04から2020/10/04の有価証券報告書をダウンロードしてxbrlファイルを抽出
poetry run python -m disclosure.main --from 2020-09-04 --to 2020-10-04 -x
ls output/*

poetry使えない人

git clone https://github.com/KAZYPinkSaurus/disclosure-crowler.git
cd disclosure-crowler
pip install requests==2.24.0 python-dateutil==2.8.1 loguru==0.5.3 click==7.1.2

#オプションを表示
poetry run python -m disclosure.main --help
# 2020/09/04から2020/10/04の有価証券報告書をダウンロードしてxbrlファイルを抽出
python -m disclosure.main --from 2020-09-04 --to 2020-10-04 -x

おわりに

EDINET APIを使って簡単にXBRLファイルをダウンロードできましたね。 XBRLファイルをダウンロードしたら次にやりたいのが構文解析してテキストマイニングじゃあありませんか? けど...難しいんでしょ? って方はこちらを読んでみるといいかもしれませんよ。

blog.hoxo-m.com

それでは。

Appendix

書類一覧APIで変えたくなるパラメータ

パラメータ名 説明
date YYYY-MM-DD ファイル日付を指定
type 1 メタデータのみを取得(typeが指定ないとこちらになる)
type 2 提出書類一覧及びメタデータを取得

書類取得APIで変えたくなるパラメータ

パラメータ名 説明 ファイル形式
type 1 提出本文書及び監査報告書を取得 ZIP
type 2 PDFファイルを取得 PDF
type 3 代替書面・添付文書を取得 ZIP
type 4 英文ファイルを取得 ZIP

参考

*1:ブラウザからダウンロードも同様

*2:ちなみに, typeを2にすると有価証券報告書のpdfファイルがダウンロードできます。 他はAppendixを参照してみてください。

有価証券報告テキストマイニング入門

はじめに

こんにちは, ホクソエムサポーターのKAZYです。 先日猫カフェデビューをして, 猫アレルギーであることがわかりました🐈。 次はフクロウカフェに挑戦してみようかなと思っています🦉。

ところで皆様, 有価証券報告書は読んでますか? 私は読んでいません。 読めません。 眺めていると眠くなります💤。

私は眠くなるんですが, 有価証券報告書ってテキストマイニングするのに向いているんです。企業の事業や財務情報が詳細に書かれています。 XBRL形式で構造化されています。 数千社分のテキストが手に入ります。 おまけに無料です。

どうです?興味湧いてきませんか?

本記事ではPythonを使って有価証券報告書をテキストマイニングする方法を紹介します。 有価証券報告書をダウンロードするところからご紹介するのでご安心を。

こんな方が見たら役に立つかも

  • 企業分析をプログラミングでやりたいが何していいか何もわからん方
  • プログラミングできるけど有価証券報告書何もわからん方
  • 自然言語処理をするのに丁度いいテキストを探している方

やること

  • 有価証券報告書についての説明
  • 有価証券報告書のダウンロード方法紹介
  • XBRLファイルについての説明
  • Pythonで有価証券報告書からテキスト抽出
  • 抽出したテキストから簡単なテキストマイニング

有価証券報告書とは

有価証券報告書, 通称「有報」👽🛸は企業の状況を外部へ開示するための資料です。 企業が自ら毎年作成しています。 企業分析をする際などによく利用されます。

提出企業

日本にうん100万社ある企業すべてが有報は提出をしている訳ではありません。

以下の条件を満たす企業だけが各事業年度毎に国へ提出を義務付けられています。

  • 金融商品取引所(証券取引所)に株式公開している会社
  • 店頭登録している株式の発行会社
  • 有価証券届出書提出会社
  • 過去5年間において、事業年度末日時点の株券もしくは優先出資証券の保有者数が1000人以上となったことがある会社(ただし、資本金5億円未満の会社を除く)

雰囲気としては上場企業+α が提出している感じです。 *1

記載内容

企業の沿革, 事業内容, 財務状況, 研究開発状況, 抱える課題, 経営方針, キャッシュフロー, 役員状況... などの情報がとても詳細に記述されています。

情報量

PDFで100ページを越えるような報告書が多いです。 例えば日本マクドナルドホールディングス株式会社の第49期の有価証券報告94ページです。 (100ページ越えてない...)

取得方法

有価証券報告の取得方法は大きく分けて以下の2つあります。

  1. ブラウザからダウンロード
  2. APIからダウンロード

今回は1.のブラウザからダウンロードする方法を紹介します。

有価証券報告書は金融庁が運営するEDINETと呼ばれるWEBサイトから自由にダウンロードができます。

皆さんご存知の日本を代表する企業, 株式会社ホクソエムの有価証券を検索してダウンロードしてみます。

該当するデータが存在しません。

と出てしまいました。

株式会社ホクソエムは有報の提出が義務付けられている会社ではありませんでした。

つぎに私が学生時代にお世話になった日本マクドナルドホールディングス株式会社を検索してみます🍔。
4件ヒットしました。 そのなかに平成27年~令和2年の有価証券報告書がありますね。 *2

XBRLと書かれたアイコンをクリックして見ましょう。 zipで圧縮されたファイルがダウンロードできます(PDFをクリックするとpdfの書類がダウンロードできます)。

ダウンロードしたzipファイルの中身

ダウンロードしたデータを展開すると以下のようなファイルが入っています。

Xbrl_Search_20200909_221659
├── S100ICBO
│   └── XBRL
│       ├── AuditDoc
│       │   ├── jpaud-aai-cc-001_E03366-000_2019-12-31_01_2020-03-30.xbrl
│       │   ├── jpaud-aai-cc-001_E03366-000_2019-12-31_01_2020-03-30.xsd
│       │   ├── jpaud-aai-cc-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│       │   ├── jpaud-aai-cc-001_E03366-000_2019-12-31_01_2020-03-30_pre.xml
│       │   ├── jpaud-aar-cn-001_E03366-000_2019-12-31_01_2020-03-30.xbrl
│       │   ├── jpaud-aar-cn-001_E03366-000_2019-12-31_01_2020-03-30.xsd
│       │   ├── jpaud-aar-cn-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│       │   ├── jpaud-aar-cn-001_E03366-000_2019-12-31_01_2020-03-30_pre.xml
│       │   └── manifest_AuditDoc.xml
│       └── PublicDoc
│           ├── 0000000_header_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── 0101010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── 0102010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── 0103010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── 0104010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── 0105010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── 0105020_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── 0106010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── 0107010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── 0200010_honbun_jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_ixbrl.htm
│           ├── images
│           │   ├── 0101010_001.png
│           │   ├── 0101010_002.png
│           │   └── 0104010_001.png
│           ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30.xbrl ←これ
│           ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30.xsd
│           ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_cal.xml
│           ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_def.xml
│           ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_lab-en.xml
│           ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_lab.xml
│           ├── jpcrp030000-asr-001_E03366-000_2019-12-31_01_2020-03-30_pre.xml
│           └── manifest_PublicDoc.xml
└── XbrlSearchDlInfo.csv

XML, HTML, CSV, XBRLファイルが入っていますね。

今回テキストマイニングに使用するのはxbrlの拡張子がついたファイルです。

XBRLファイルはAuditDoc,PublicDocの2つのディレクトリ内に入っています。 それぞれ有価証券報告書内の監査に関する報告, それ以外の報告が記載されています。

XBRLファイルとは

XBRLファイルについて, テキストマイニングに必要最低限の説明します。 XBRLファイルとはXMLをベース に事業報告用に拡張したマークアップ言語です。*3

実際にXBRLファイルを覗いてみると,

<?xml version="1.0" encoding="UTF-8"?>
<xbrli:xbrl>
︙~中略~
</xbrli:xbrl>

と記述されています。 一行目に思いっきりxmlと書いてあることが確認できると思います。 そういうことです。

また, 有報に記述する基本的な名前空間とタグは定義されており, タクソノミ要素リストで確認できます(リンクは2020年版)。

タグが統一されているので様々な企業が提出するXBRLファイルを一度にプログラムで処理しやすいのです。 素敵です。

具体例として, 有報表紙の企業名部分の名前空間とタグはそれぞれjpcrp_cor,CompanyNameCoverPageで書きましょうと決められております。

実際にマクドナルドとモスバーガーのXBRLファイル内の対象部分を見ると,

<jpcrp_cor:CompanyNameCoverPage contextRef="FilingDateInstant">日本マクドナルドホールディングス株式会社</jpcrp_cor:CompanyNameCoverPage>

<jpcrp_cor:CompanyNameCoverPage contextRef="FilingDateInstant">株式会社モスフードサービス</jpcrp_cor:CompanyNameCoverPage>

とどちらも同じタグの中に会社名が囲まれていますね。

これでどんな情報がどんなタグで表現されているかがわかるので早速プログラムを書いてテキスト抽出していきましょう。

XBRLファイルについてもっと詳しく知りたいという方用にオススメ記事リンク貼っておくので読んでみてください。

👇👇👇👇👇👇👇👇👇👇👇👇

note.com

note.com

note.com

Pythonを使って任意の項目を抽出する

次に, Pythonを用いてXBRLファイルから任意の項目のテキストを抽出します。 今回はlxmlというライブラリを使います。 *4

【会社名】項目の抽出

会社名をXBRLファイルから抽出してみます。 会社名は有価証券報告書の表紙に書いてあります。 以下のスクリプトで抽出きます。

from lxml import etree
mcdonalds_xbrl = "./Xbrl_Search_20200914_000607/S100IW0P/XBRL/PublicDoc/jpcrp030000-asr-001_E02675-000_2020-03-31_01_2020-06-25.xbrl"

# xbrlファイルを指定してパース
tree = etree.parse(mosuburger_xbrl)
root = tree.getroot()
# 名前空間とタグを指定して検索
company_name = root.find("jpcrp_cor:CompanyNameCoverPage",root.nsmap).text

>> 日本マクドナルドホールディングス株式会社

簡単ですね。 XBRLファイルをモスフードサービスのものに変更すると,

from lxml import etree
mosuburger_xbrl = "./Xbrl_Search_20200914_000607/S100IW0P/XBRL/PublicDoc/jpcrp030000-asr-001_E02675-000_2020-03-31_01_2020-06-25.xbrl"

# xbrlファイルを指定してパース
tree = etree.parse(mosuburger_xbrl)
root = tree.getroot()
# 名前空間とタグを指定して検索
company_name = root.find("jpcrp_cor:CompanyNameCoverPage",root.nsmap).text

>> 株式会社モスフードサービス

同じコードで会社名が取り出せました。

【事業の内容】項目を抽出

次に事業の内容が記載された項目の抽出を行ってみます(上図)。

タクソノミ要素リストから事業の内容のタグを調べます。jpcrp_cor:DescriptionOfBusinessTextBlockのタグでできると書いてあります。 検索するタグを変更してスクリプトを実行します。

from lxml import etree
mcdonalds_xbrl = "./Xbrl_Search_20200914_000607/S100IW0P/XBRL/PublicDoc/jpcrp030000-asr-001_E02675-000_2020-03-31_01_2020-06-25.xbrl"

# xbrlファイルを指定してパース
tree = etree.parse(mosuburger_xbrl)
root = tree.getroot()
# 名前空間とタグを指定して検索
bussiness_block = root.find("jpcrp_cor:DescriptionOfBusinessTextBlock",root.nsmap).text

>> '\n<h3>3【事業の内容】</h3>\n<p style="margin-left: 24px; text-align: left">\n<span style="font-family: &apos;MS Mincho&apos;; font-size: 12px">\u3000当社グループの事業はハンバーガーレストラン事業単一であるため、セグメント情報に関連付けた記載を行っていません。</span>\n</p>\n<p style="margin-left: 24px; text-align: left">\n<span style="font-family: &apos;MS Mincho&apos;; font-size: 12px">(当社の事業内容)</span>\n</p>\n<p style="margin-left: 24px; text-align: left">\n<span style="font-family: &apos;MS Mincho&apos;; font-size: 12px">\u3000当社は、日本マクドナルド株式会社の持株会社として、グループ企業の連結経営戦略の策定業務と実行業務及び不動産賃貸業務を主たる事業としております。</span>\n</p>\n<p style="margin-left: 24px; text-align: left">\n<span style="font-family: &apos;MS Mincho&apos;; font-size: 12px">\u3000なお、当社は特定上場会社等であります。特定上場会社等に該当することにより、インサイダー取引規制の重要事実の軽微基準については連結ベースの数値に基づいて判断することとなります。</span>\n</p>\n<p style="margin-left: 24px; text-align: left">\n<span style="font-family: &apos;MS Mincho&apos;; font-size: 12px">(関係会社の事業内容)</span>\n</p>\n<p style="margin-left: 24px; text-align: left">\n<span style="font-family: &apos;MS Mincho&apos;; font-size: 12px">\u3000日本マクドナルド株式会社(当社出資比率100%)は、直営店方式による店舗運営とともにフランチャイズ方式による店舗展開を通じハンバーガーレストラン事業を展開しております。同社は、米国マクドナルド・コーポレーションから許諾されるライセンスに対するロイヤルティーを支払っております。日本国内においては、フランチャイズ店舗を経営するフランチャイジーに対してノウハウ及び商標等のサブ・ライセンスを許諾し、フランチャイジーからロイヤルティーを収受しております。</span>\n</p>\n<p style="margin-left: 24px; text-align: left">\n<span style="font-family: &apos;MS Mincho&apos;; font-size: 12px; font-weight: normal">\u3000当社と関係会社との当連結会計年度における資本関係及び取引関係の概要は、以下のとおりであります。</span>\n</p>\n<p style="margin-left: 24px; text-align: left">\n<span style="font-family: &apos;MS Mincho&apos;; font-size: 12px">[事業系統図]</span>\n</p>\n<p style="text-align: center">\xa0</p>\n<p style="text-align: left">\n<img style="height: 441px; width: 566.195739746094px" src="images/0101010_002.png" alt="0101010_002.png"/>\n</p>\n<p style="text-align: left">\xa0</p>\n<p style="text-align: left">\xa0</p>\n'

html形式の文字列が抽出されました。 XBRLの各タグの要素をhtml形式で表現しているものが多く存在します。 表形式の情報などはhtml形式としてパースを行って目的の情報を抽出できます。 今回はhtmlタグを取り除く処理を行います(ついでに改行コードも除く)。

# 正規表現操作のライブラリ
import re
# 改行や空白文字を削除する
bussiness_block = re.('\s','',bussiness_block)
# htmlタグを削除する
bussiness_block = re.('<.*?>','',bussiness_block)

>>'3【事業の内容】当社グループの事業はハンバーガーレストラン事業単一であるため、セグメント情報に関連付けた記載を行っていません。(当社の事業内容)当社は、日本マクドナルド株式会社の持株会社として、グループ企業の連結経営戦略の策定業務と実行業務及び不動産賃貸業務を主たる事業としております。なお、当社は特定上場会社等であります。特定上場会社等に該当することにより、インサイダー取引規制の重要事実の軽微基準については連結ベースの数値に基づいて判断することとなります。(関係会社の事業内容)日本マクドナルド株式会社(当社出資比率100%)は、直営店方式による店舗運営とともにフランチャイズ方式による店舗展開を通じハンバーガーレストラン事業を展開しております。同社は、米国マクドナルド・コーポレーションから許諾されるライセンスに対するロイヤルティーを支払っております。日本国内においては、フランチャイズ店舗を経営するフランチャイジーに対してノウハウ及び商標等のサブ・ライセンスを許諾し、フランチャイジーからロイヤルティーを収受しております。当社と関係会社との当連結会計年度における資本関係及び取引関係の概要は、以下のとおりであります。[事業系統図]'

事業の内容項目をきれいに抽出することができました。 ファイルをモスフードサービスに変更しても,

"3【事業の内容】当社グループは、㈱モスフードサービス(当社)及び子会社12社、関連会社14社により構成されており、主にフランチャイズシステムによる飲食店の展開を事業としております。事業は大きく「モスバーガー」等の商標を使用した飲食店を展開する「モスバーガー事業」、「マザーリーフ」「MOSDO」「ミアクッチーナ」「あえん」「chef'sV」「GREENGRILL」等の商標を使用した飲食店を展開する「その他飲食事業」、これらの飲食事業を衛生、金融、保険等で支援する「その他の事業」に分けることができます。事業内容と当社及び関係会社等の当該事業における位置付け及びセグメントとの関連は、次のとおりであります。セグメントの名称主要製品主要な会社モスバーガー事業「モスバーガー」等の運営ハンバーガー、ライスバーガー、モスチキン、スープ、ドリンク等及びパティ、バンズ、ポテト等の食材並びにカップ、パッケージ等の包装資材[国内]㈱モスフードサービス㈱モスストアカンパニー[台湾]安心食品服務(股)[シンガポール]モスフード・シンガポール社安心フードサービスシンガポール社[香港]モスフード香港社香港モスバーガーインベストメント社[中国]広東摩斯貝格餐飲管理有限公司[タイ]モスバーガー・タイランド社[オーストラリア]モスバーガー・オーストラリア社[インドネシア]モグインドネシア社[韓国]モスバーガーコリア社[フィリピン]モスバーガー・フィリピン社食品製造、食材販売事業パティ、ソース類等[国内]紅梅食品工業㈱タミー食品工業㈱[台湾]魔術食品工業(股)[フィリピン]モスサプライ・フィリピン社アグリ事業トマト、レタス等[国内]㈱モスファーム熊本㈱モス・サンファームむかわ㈱モスファームすずなり㈱モスファームマルミツ㈱モスファーム信州㈱モスファーム千葉その他飲食事業喫茶紅茶、ワッフル、パスタ、スイーツ等[国内]㈱モスフードサービス㈱モスストアカンパニーレストラン和風旬菜料理、洋風旬菜料理等[国内]㈱モスフードサービス㈱モスダイニングその他の事業食品衛生検査業ハンバーガー等の衛生検査、衛生関連商品の販売[国内]㈱エム・エイチ・エス金銭貸付業フランチャイジー(加盟店)への事業資金貸付[国内]㈱モスクレジット保険代理業生命保険、損害保険[国内]㈱モスクレジットレンタル業POSレジスター、看板等[国内]㈱モスクレジットグループ内アウトソーシング事業グループ内アウトソーシング事業[国内]㈱モスシャイン以上の企業集団等について事業系統図を図示すると次のとおりであります。(注)海外における事業は「モスバーガー事業」であります。子会社及び関連会社の連結の範囲は、次のとおりであります。子会社関連会社㈱エム・エイチ・エス※紅梅食品工業㈱㈱モスクレジット※タミー食品工業㈱㈱モスストアカンパニー※安心食品服務(股)㈱モスダイニング※モスバーガー・オーストラリア社㈱モスシャイン※モスバーガーコリア社モスフード・シンガポール社※モスバーガー・タイランド社魔術食品工業(股)※モスバーガー・フィリピン社モスフード香港社㈱モスファーム熊本モスサプライ・フィリピン社㈱モス・サンファームむかわ※(モグインドネシア社)㈱モスファームすずなり※(香港モスバーガーインベストメント社)㈱モスファームマルミツ※(広東摩斯貝格餐飲管理有限公司)㈱モスファーム信州㈱モスファーム千葉安心フードサービスシンガポール社計12社計14社(注)1.()内は非連結子会社であります。2.※印は持分法適用会社であります。"

この通りきれいに内容を抽出することができました。

これでテキストの抽出ができるようになりました。

企業の課題を用いてワードクラウドを作ってみる

簡単なテキストマイニングをやってみます。 異なる2つの業界の企業から有価証券報告書の【経営方針、経営環境及び対処すべき課題等】の項目を抽出して頻出ワードを抽出してワードクラウドを作ります。

各業界のおかれている状況の違いみたいなのが見えたらいいなぁというお気持ちです。

まず,

  • SaaS企業売上の高そうな10社
  • “ゴルフクラブ”と名のついた企業10社

の有価証券報告書を用意します。

【経営方針、経営環境及び対処すべき課題等】項目のテキストを抽出します。

1文中の将来に関する事項は、当事業年度末現在において当社が判断したものであります。(1)経営方針①ゴルフ場は会員様(株主)の財産であるとの意識を高く持ち、そのハード・…略

形態素解析して名詞だけ抽出します(mecab-python3を使用)。

1文中 将来 事項 事業年度末現在 当社 判断 もの * 経営方針*ゴルフ場 会員様 株主 財産 意識 ハード …略

そこからを用いてワードクラウドを作ります(wordcloudを使用)。

SaaS企業

ゴルフ場経営企業

ワードクラウドが作成できました。 売上のあるSaaS経営企業とゴルフ場経営企業では頻出するワードの雰囲気がぜんぜん違いますね。

名詞ではなくて形容詞を用いて同様にワードクラウドを作ってみました。

SaaS企業

ゴルフ場経営企業

ところゴルフ場経営企業の厳しさが伝わってきました。

👇ソースコードはこちら👇 github.com

おわりに

今回は有価証券報告書からテキストを抽出して単なテキストマイニングを紹介しました。 次のステップとして有価証券報告書から財務情報を抽出がオススメです。 html形式から情報抽出する技術が身につけられます。

*1:ちなみに日本の上場企業数は4000社程度です

*2:ちなみに有価証券報告書の電子提出が義務付けられたのは2004年6月からですがEDINETでは直近5年間の有価証券報告書しかダウンロードできません。 全人類が思いつくであろうある企業の過去15年分の有価証券報告書を使って分析というのができないのは少し残念です。 実はこんなサイトには直近5年以上前の情報もあったりします。

*3:そもそもXMLを知らない方はこことかを読んで見るといいかもしれません。

*4:標準ライブラリに入っているxmlではなくlxmlを使用した理由は名前空間のURIマップをライブラリで取得することができるからです。

MLflowのXGBoost拡張を読んでみる

はじめに

ホクソエムサポーターの藤岡です。会社を移りましたが、相変わらずPythonを書く仕事をしています。

前回の記事に引き続き、今回もMLflowについての記事です。 前回はトラッキング寄りでしたが、今回はモデルのデプロイにも関わってくる内容です。

MLflowはXGBoost, PySpark, scikit-learnといった多様なライブラリに対応していて、様々な機械学習タスクに活用することができるのが売りの一つです。 その実現のため、設計や実装に様々な工夫がされているのですが、 この部分について詳しくなることで、オリジナルの機械学習モデルをMLflowとうまく繋ぐことができるようになったり ETLのようなモデル学習にとどまらない使い方もできるようになったりします。

本記事では、XGBoostをMLflowで扱うためのモジュール mlflow.xgboost について解説することで、拡張性の鍵であるflavorについて紹介します。 また、その前提知識として、MLflow Model, PyFunc Modelについても関連する部分をさらっと解説します。

機械学習が完全に絡まない部分で使う必要があるのかはさておき、 機械学習が絡んでいるフローを広く一つのフレームワークで管理できるようにしておくのは有用だと思います。 実際、MLflowのサンプルコードにもETLを含めて管理している例があります*1

本記事を通してMLflowについて理解を深め、活用の幅を広げましょう!

なお、本記事はMLflowの使用方法と前回記事の内容 (のうちMLmodel周り) が前提知識となります。 また、MLflow v1.10.0 時点での内容です。

MLflow Model

今日では、日々新しいMLモデルが提案され、それらを実装したパッケージも次々と生まれています。 それらはsklearnの提供する形式に従っているものから、 xgboostのようなデータ構造まで自前で用意してパッケージングしたものまで様々であり、 統一的な形式のようなものはありません。

MLflowは抽象的なモデルフォーマットを定義し、それを通じてこれらのフレームワークやパッケージを統一的なインターフェースで扱います。 このモデルフォーマットは MLflow Model と呼ばれます。 モデルとは言っても機械学習モデルというよりそれにまつわる種々の内容を定めたものです。 以下の例をはじめとする種々の項目についてそれぞれ形式を定めています。

  • ストレージ
  • モデルの入出力形式(Signature)
  • モデルのAPI (セーブ、ロード、ロギング等)

このフォーマットはmlflow.models以下で実装されています。 例えば、本体となるクラスはmlflow.models.model.pyの中にModel()クラスとして定義されています。 ちなみに、このクラスがどんなメソッドやパラメータを持っているか一通り目を通しておくと、MLflow Modelについて少しイメージがしやすくなります。

flavor

MLflow Modelは抽象度が高く、それ単体では使うことができません。 実際、モデルのデプロイ部分の実装を見てみると、 入力とメソッド名のみを定義している以外はすっからかんとなっています。

そのため、MLflow Modelは既存の機械学習フレームワーク等のモジュールやスクリプトがこのフォーマットに従うようにするためのインターフェースを必要とします。 これを、flavorと呼びます。 例えば、xgboost.Booster (xgboostのモデルクラス) をMLflowで扱う場合、Boosterに特定のメソッドを生やすためのラッパなどをflavorとして実装すれば、 MLflowの学習トラッキングやモデルデプロイなどの機能をxgboost.Boosterに対して実行できます。

とはいっても、xgboost.Boosterについてはmlflow.xgboostモジュールとしてflavorが既に定義されているので、 ユーザ側ではただmlflow.xgboostの関数を呼び出すだけで使えます。

さらに、あるflavorをベースに別のflavorを実装することも可能です。 ベースにするといっても、実際には両方を併用するような形になります。

flavorの種類

flavorは大きく3種類に分けることができます。

Built-in flavor

XGBoostを始めとして、MLflowが対応している種々のモジュールに対してそれぞれflavorが定義されています。 一般的なデータ分析タスクで使うような機械学習モデルはたいていの場合はこれらのflavorで対応ができるので、 小さいコーディングコスト(log_model()save_mode()を呼び出すだけ)でMLflowの提供する機能を十全に使うことができます。

さらに、いくつかのflavorにはautolog機能を備えたflavorがあり、 その場合はほぼ全自動でMLflowの機能を使うことができます。

PyFunc Model flavor

PyFunc Modelとは、特定の機械学習モデルを指すモデルではなく、以下の入出力をもつ関数の抽象モデルです。

predict(model_input: pandas.DataFrame) -> [numpy.ndarray | pandas.(Series | DataFrame)]

これ自身を使うのではなく、これをベースに別のflavorを作るために用意されています。 上記の入出力をもつ関数として表現可能なものであればこのflavorを元に作成可能です。 全てのBuilt-in FlavorはPyFunc Model Flavorをベースに作成されています。

MLflowのdocumentやソースコードを読んでいるとき、PyFunc ModelとMLflow Modelを混同しやすいので注意しましょう。 基本的にはPyFunc ModelがMLflow Modelの一部だとみて問題ないとは思いますが、 とにかくmodelという単語がたくさん出てきて混乱するので区別を付けられるようになっておくのがいいです。

ここでは紹介しませんが、PyFunc Modelの詳細について知りたい方はこちらこちら が詳しいです。 backendの実装等を覗いてみると、PyFunc Model flavorが非常に多くの機能を実装し、その他のflavorの作成を助けているのかが見て取れておもしろいです。

Custom Python flavor

MLflowが提供しているflavor以外にもユーザ側がflavorを開発することでMLflow互換のモデルを定義することが可能です。

作成方法は以下の2通りがあります。

  1. 実装したいモデルのクラスをmlflow.pyfunc.PythonModelのサブクラスとして作成
  2. 既存のモデルをPyFunc Modelと相互変換するような入出力関数を定義

基本的には1が簡単かつ有用ですが、MLflowを使わないで保存したモデルオブジェクトとの互換性はないので、既に学習済みのモデルを取り込むなどの互換性が必要な場合は2の方法を採る必要があります。

XGBoost flavor

Overview

f:id:kazuya_fujioka:20200817090002p:plain
XGBoost Flavorとその関連する要素

図は、XGBoost flavor, MLflow, XGBoostの三者、そしてflavorを呼び出すスクリプトの4要素がどのオブジェクト (関数/クラス/モジュール) を通じて繋がっているのかを図示したものです。 黄色い四角で囲われた6つの関数がXGBoost flavorの主な構成要素です((autolog()等は本質ではなく、理解も容易なので省略しています。))。 なお、分かりやすさのためPyFunc Modelの実装であるpyfuncモジュールもMLflowに入れています。

ユーザ側はlog_model()(MLflow Modelの記録)、load_model()(モデルの読み込み)を操作します。 内部的には、log_model()の呼び出しでsave_model()(MLflow Modelの書き出し)が内部的に呼び出されるほか、_load_pyfunc()はモデルのデプロイ時に内部的に呼び出されます。

これらは、基本的には全てのflavorに共通です。

一方で_load_model()_XGBModelWrapper()はxgboostライブラリに固有の実装です。 XGBoost flavor内から必要に応じて呼び出されます。

Components

では、これらを順を追って詳細に読んでいきます。 なお、解説のために動作が変化しない程度にコメントや変数等を変更している部分があります。

_load_pyfunc() & _load_model() & _XGBModelWrapper

def _load_pyfunc(path):
    xgb_original = _load_model(path)
    return _XGBModelWrapper(xgb_original)


def _load_model(path):
    import xgboost as xgb
    model = xgb.Booster()
    model.load_model(os.path.abspath(path))
    return model


class _XGBModelWrapper:
    def __init__(self, xgb_model):
        self.xgb_model = xgb_model

    def predict(self, dataframe):
        import xgboost as xgb
        return self.xgb_model.predict(xgb.DMatrix(dataframe))

これらの3つの関数で実現していることは、引数で与えられたパスpathにある学習済みxgboostモデルをロードする処理を、 _load_pyfunc()という名前の関数として実装することです。

_load_pyfunc()関数はpyfunc.__init__.py内に定義されているload_model()関数内で呼び出され、 PyFuncModelクラスの初期化に使用されます (実装)。

モデルのロード部分はxgboostライブラリの機能をそのまま使って_load_model()関数として実装しています。 ただし、xgboost.Booster()predict()メソッドを実装しているものの、その入力がPandas DataframeではなくDMatrixなのでそのままではPyFuncModelに渡せません。 その間を埋めるためのラッパとして_XGBModelWrapperクラスが定義されています。

save_model()

def save_model(xgb_model, path, conda_env=None, mlflow_model=None,
               signature: ModelSignature=None, input_example: ModelInputExample=None):
    import xgboost as xgb

    path = os.path.abspath(path)
    if os.path.exists(path):
        raise MlflowException("Path '{}' already exists".format(path))
    os.makedirs(path)
    if mlflow_model is None:
        mlflow_model = Model()
    if signature is not None:
        mlflow_model.signature = signature
    if input_example is not None:
        _save_example(mlflow_model, input_example, path)
    model_data_subpath = "model.xgb"
    model_data_path = os.path.join(path, model_data_subpath)

    # Save an XGBoost model
    xgb_model.save_model(model_data_path)

    conda_env_subpath = "conda.yaml"
    if conda_env is None:
        conda_env = get_default_conda_env()
    elif not isinstance(conda_env, dict):
        with open(conda_env, "r") as f:
            conda_env = yaml.safe_load(f)
    with open(os.path.join(path, conda_env_subpath), "w") as f:
        yaml.safe_dump(conda_env, stream=f, default_flow_style=False)

    pyfunc.add_to_model(mlflow_model, loader_module="mlflow.xgboost",
                        data=model_data_subpath, env=conda_env_subpath)
    mlflow_model.add_flavor(FLAVOR_NAME, xgb_version=xgb.__version__, data=model_data_subpath)
    mlflow_model.save(os.path.join(path, MLMODEL_FILE_NAME))

MLflow Modelmlflow_modelをartifact化し、渡されたローカルのフォルダパスpathに保存する関数です。 具体的には、フォルダの中に以下のファイルが作成されます。

  1. signatureとして渡されたSignatureのダンプファイル
  2. xgb_modelとして渡されたxgboost.Boosterのダンプ。
  3. conda_envとして渡された辞書/ファイル名のconda envファイル (ただし、無ければデフォルトを生成)
  4. MLflow Modelファイル(MLmodel)

最も重要なのは、以下の3行です。

pyfunc.add_to_model(mlflow_model, loader_module="mlflow.xgboost",
                        data=model_data_subpath, env=conda_env_subpath)
mlflow_model.add_flavor(FLAVOR_NAME, xgb_version=xgb.__version__, data=model_data_subpath)

MLmodelファイル(yaml)にはflavorごとの設定項目を記録するセクションがあり、 その内容をここで生成しています。

あるflavorの設定項目を記録するには、Model.add_flavor()メソッドを使って、 flavor名(第一引数)とその設定項目(キーワード引数)を渡します。

例えば、3行目で、XGBoost flavorの項目が以下のように作成されます。

xgboost:
    xgb_version: <xgb.__version__の値>
    data: subpath/from/MLmodel/root/to/model.xgb

なお、セクション名"xgboost"はmlflow.xgboost内で

FLAVOR_NAME = "xgboost"

と定義されているところから来ています。

さらに、PyFunc Model flavorをベースに作られているflavorを使った場合、 PyFunc Modelのセクション"python_function"を作成します。

1-2行目の"pyfunc.add_to_model()"関数は"Model.add_flavor()"メソッドの呼び出しをPyFunc Model flavor向けにラップしたものです。 この中で conda env等のPyFuncの基本的な構成要素へのパスとPythonのバージョンを含んだ"python_function"セクションがmlflow_modelに記録されます。

log_model()

def log_model(xgb_model, artifact_path, conda_env=None, registered_model_name=None,
              signature: ModelSignature=None, input_example: ModelInputExample=None,
              **kwargs):
    Model.log(artifact_path=artifact_path, flavor=mlflow.xgboost,
              registered_model_name=registered_model_name,
              xgb_model=xgb_model, conda_env=conda_env,
              signature=signature, input_example=input_example, **kwargs)

mlflow.models.Modellogメソッド(classmethod)のラッパです。 大まかには、以下の3ステップを実行します。

  1. MLflow Modelの生成
  2. artifactの生成
  3. 生成したMLflow Model(artifactを含む)をrunとして記録

なお、モデルのartifact化に使用するパラメータはModel.logkwargs引数に入ってからflavor.save_model()呼び出し時に渡されます。 save_model()xgb_model変数はkwargsを通じてsave_model()に渡され、artifact化されます。

load_model()

def load_model(model_uri):
    local_model_path = _download_artifact_from_uri(artifact_uri=model_uri)
    flavor_conf = _get_flavor_configuration(model_path=local_model_path, flavor_name=FLAVOR_NAME)
    xgb_model_file_path = os.path.join(local_model_path, flavor_conf.get("data", "model.xgb"))
    return _load_model(path=xgb_model_file_path)

この関数の処理内容ですが、

  1. artifactをダウンロードして
  2. flavorの設定を読み込んで
  3. xgboost.Boosterをartifactからロード

と、中で呼び出されている関数名を読んだ通りの処理内容です。

特に解説することもないのですが、 _download_artifact_from_uri()_get_flavor_configuration()といった 便利なutility関数が実装されていることを知っているとflavor実装などで役に立ちます。

おわりに

駆け足となりましたが、XGBoost flavorの内容とその前提となる知識を軽く紹介しました。 シンプルなflavorなので、MLflowの理解のために読むのはもちろんですが、flavorを作ってみる際の手本としても最適だと思います。 実際、今回書いていてすごく理解が深まり、前回記事の内容が思いっきり間違っていることにも気付けました。

当該箇所は修正済みです。本当にすみませんでした……。

他にも、純粋にPythonモジュールの実装としても、多層に分けて段階的に抽象度を下げていくなど勉強になることが多かったです。 汎化と実装コストのトレードオフにはいつも苦心しているので、今後書くプログラムに色々と取り入れようと思っています。

では、良きPython Lifeを!

Reference

*1:この例では、今回の記事の内容は不要です

私の人生のロックマン(あるいは星のカービィ)戦略について

株式会社ホクソエム常務取締役のタカヤナギ=サンです。

会社では主にα崩壊を起こしそうなシャチョーを制御するための制御棒を担当しています。

これは何の話なの?

私のやり方というか能力の上げ方はタイトルにあるように基本的に「ロックマン(あるいは星のカービィ)戦略」なんですが、それについて明示的に書いたポエムです。

勘の鋭い方はこのタイトルだけでどういう戦略なのかお分かりになられると思うので、ここでこの記事を読むのをストップすれば作業時間を確保できて良いかもしれない。

「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?

とりあえずの結論

なんか長いんでとりあえず結論を書くと

  • 技術をたくさん持ってる/生み出してそうな優秀な技術者(エンジニア)に暗に陽に弟子入りしてたくさんの技術(Not技能)をコピーさせてもらおう!
  • 高度な技能(スキル)を持っている技能者(テクニシャン)さんを真似ようとする場合には「彼はコピー可能なもの(テクノロジーのレベルにまで至っているもの)を持っているのか?」を意識しよう
    • もしそうでないならばその人からコピーして学ぶことをやめよう、それは時間の無駄です
      • 技能(スキル)は定義からしてコピー不可能なものだから
      • このインターネット時代においては学ぶべきリソースは山ほど転がっているから

です。

私の人生のロックマン(あるいは星のカービィ)戦略について

↓こんなかんじでロックマンって敵(BOSS)を倒すとそいつの能力使えるようになるじゃないですか?

youtu.be

あるいは、カービーでも吸い込だ敵の能力コピーできるじゃないですか?

youtu.be

これらと同じように、私の人生戦略って基本的に単純で「良いな・イケてるなと思った人のアイディア・やり方・技術・振る舞いを何でもコピーする」って話しなんですね。

あるいはバトルボーナス中にトキを降臨させちゃう北斗の拳のケンシロウ(パチスロ)とかとか。

www.youtube.com

「こ…この動きは…ト…トキ!」

「ラオウよ、天に還る時が来たのだ」

最高でしたね???あの頃は皆トキの動きのマネしてた。バトルボーナス、確定!!!

なんでこのやり方をするのか?それは私に個性がないからです

なんでこういうコピー戦略をするのかというと、それは私に個性がないからです。

私はキャンバス、ほぼ真っ白なキャンバス。

私を知ってる人は「いや、お前髪が変な色だったり大体似たような熊のTシャツ来てるし個性的じゃねぇかよ」と思うかも知れないが、それは違う。

私の髪の色が変なのは親愛なる美容師殿への正しい課金手段として「好きな色にしていいよ(ブリーチやカラー入れた方が課金額があがる)」からやっているのであって特に私の好みではないし、逆に常に黒い髪をKeepされ続けている皆さんのほうがよほど個性的だ。

”髪 is 黒いであるべし”という強いこだわりを感じる。もしこだわりがないというのなら適当な色にされたってよくない?*1

もう少し言うとオリジナルの学び方というものを知らない。これはあれ、文化的資本って呼ばれているやつに近いと思う。 書籍を読むようになったのは大学院に入った時(24歳)でそれまではいわゆる教科書と読書感想文で嫌々読んだ本くらいしか読んだことがなかった。 なので、そもそも学び方を知らないので、人からコピるしかなかったわけです。 メタ学習(学び方を学ぶ)みたいな概念もなかった、メタ?なにそれ美味しいの?

今ではおかげさまで書籍を買う余裕があるのでいっぱい本読んでるけど。

もちろん私にもコピーして塗りつぶしたくない色や領域、そして大切にしているものがあると言えばあるが、それはそんなに多くないんですね。

大多数の物事に対するこだわりを捨てる、特定の領域にしぶとく深く興味を持ち続ける、その他は基本人の全コピーか脳死状態で行動できるよう仕組み化にしておく、それが俺のやり方。

ちなみに、このやり方を取った場合にみなさんがメリットと感じそうなこととしては”アンチ耐性の飛躍的な向上”があります。

すごい!RPGだと最後の方に出てきそうな高級なアクセサリー的装備が手に入ります。

例えば、最近おかげさまである程度書籍などが売れまして、インターネットにおいてもHOXO-Mアンチが沸いてきており、

  • 「くそっ、ホクソエムむかつく!」

とか

  • 「なんか〜ホクソエム苦手〜生理的に無理〜」

みたいな話がちょいちょいあるんだが、私はこの手の話はイチロー先生よろしくどんと来いだ。

www.youtube.com

何故どんとこいなのかというと、最近よく聞く「ネットの誹謗中傷で慰謝料が取れて儲けのチャンスだワハハ!」という点はおいておくとして、上述したように私に個性がないからですね。

もう少し言うと「皆さんが見ている・思っている私の個性的なものは大体私が他人から拝借したアイディア・やり方・技術・振る舞いであるので、 そもそも特に私自身がアンチにディスられているとは感じない」わけです。

「あっ、あの時助けていただいた鶴さんのアイディア・やり方・技術・振る舞いをディスってるんだなこいつらは」くらいのイメージです。 なので虚無。何も感じない。

もちろん私にもディスられたら嫌な興味関心のあるところがあるような気もするが、 それはそもそも大多数の社会を営んでいる皆様におかれましてはAlmost surely興味も関心もない話(特に広義の統計科学に関わるところ)なので、それがディスられることはまずないという状況なんですね、はい。

じゃぁ、貴方は誰からロックマン/星のカービィ戦略に基づいてコピーをするの?

ここまで何度か”コピーをする”と書いてきたが、その対象は何度か書いたようにある人の”アイディア・やり方・技術・振る舞い”です。 エンジニアリングにのみ対象を絞るとすると、コピーするするものは”技術(テクノロジー)”であって、”技能(スキル)”ではない、ここが重要です。

なんでかと言うと技能(スキル)はコピーできなくて技術(テクノロジー)のみがコピー(人から人へと伝達)可能だからですね。

「じゃぁ、技能と技術って何が違うんだ?」と言うと、昔、私も何度か調べてはいずれの回も厳密な定義にたどり着けずに力尽きているのだが、 ここではとりあえずWikipediaから”技術”と”技能”の違いについての定義を拝借するとしましょうか。

技術と技能(スキル)の違い
「技術」と「技能」という言葉は、違いを理解せずに混同して会話などで使用されがちであるが、以下のように明確な違いがある。
技術
    知識のことである。
    教科書のように文書化したり、会話などで他人に伝達可能である。
技能
    技術(知識)を使用し、作業を遂行する能力のことである。
    個人の中に熟成されるため、他人に伝達不可能である。

例えば、自動車の運転の仕方を知識として習得しただけでは、自動車を正確に運転できる確率は低い。訓練を行い、運転の技能を上げることにより、より正確に運転できるようになる。

技術 - Wikipedia

・・・とさ。 なるほど、他人に伝達可能か否かが違うぞと、そういうことです。

そもそもテクノロジー(技術)という語はギリシア語の「技(テクネ)を学問(XXXオロジー)のレベルにまで昇華したもの」という意味なので、ちゃんと原義に沿っていますね? なので、技術(テクノロジー)のみがコピー可能で技能(スキル)はコピー不可なわけです、人に宿る職人芸、強し。

じゃぁ技術者(エンジニア)ってのは何なのかと言うと同じくWikipediaを参考にすると

現代ではエンジニアとは、自然法と社会の必要性の制限の中でテクノロジーを創り出す人のことをいう。

技術 - Wikipedia

ということだそうだ*2

なんで、長いこと社会生活をしているとちょいちょい

  • 「いや〜XXXさんのCodeは良く高速に動きますが難解ですな、可読性0ですわいタハー(それメンテできなくね?)」

とか

  • 「俺の!コードは!読めば分かる!だから!ノーマニュアルや!(実際は難解&README.md書くくらいまではどう考えても仕事じゃね?)」

などと元気よく自慢してくるおじさんに出会うのだが、私は基本的にそれらの方々は無視。

だって、それエンジニア(技術者)じゃなくてテクニシャン(技能者)だもん、コピーできるもの持ってないんだもの(いや持ってるかもしれないけどその凄いポイントはコピー不可なんだもの)。

頑張ってチーフテクニシャンおじさん(CTO)、ゆくゆくは人間国宝を目指してもらいたい。 海老蔵の睨みばりのSomething技能を研ぎ澄ませて行ってほしい。 一方、優秀な技術者が優秀な技能者を兼ねている場合も当然多いので、その場合にはとても用があります。

そんなかんじ。

結論

長くなったが私のやり方をまとめると

  • 技術をたくさん持ってる/生み出してそうな優秀な技術者(エンジニア)に暗に陽に弟子入りしてたくさんの技術(Not技能)をコピーさせてもらおう!
  • 高度な技能(スキル)を持っている技能者(テクニシャン)さんを真似ようとする場合には「彼はコピー可能なもの(テクノロジーのレベルにまで至っているもの)を持っているのか?」を意識しよう
    • もしそうでないならばその人からコピーして学ぶことをやめよう、それは時間の無駄です
      • 技能(スキル)は定義からしてコピー不可能なものだから
      • このインターネット時代においては学ぶべきリソースは山ほど転がっているから

ということでした。

次回もまた、見てくれよな!

*1:実は一回全頭ドピンクにされたことがあって、そのときだけは鏡を見るたびになんか不安な気持ちになったので「ピンクだけはやめてくれ」と言うようになった。”髪の色がピンクなのは嫌だ”が私の個性かもしれない

*2:この辺の工学(者)・技術(者)の関係がWikipediaさんも曖昧で私も調べても曖昧なので誰か正しい定義を知ってたら教えて欲しい

TRI-AD(TOYOTAの自動運転のとこ)の服部圭悟さんにカジュアル面談してもらった

頭出し

前職の同僚(一時期私の真後ろの席に座っていた)で、今は「誰もが、安全に移動できる世界へ」を掲げるTRI-ADに勤めている服部圭悟さんとカジュアル面談したら面白かったのでまとめておきたい、そして彼のチームの採用へとつなげていきたい。

カジュアル面談中にメモった箇条書きを体裁整えただけなので、やや文が壊れているがご容赦&そこも含めて愛して欲しい。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?

このカジュアル面談では、会社の全容とかまるで聞かないで私の聞きたいことだけ聞いてきたんで、その質疑応答をまとめました。 本当に好き勝手に聞いたのにまじで全部真摯に答えてもらって大変ありがたかったです。

みんなが話を聞きに行くとまた違った結果になるかもしれない、カジュアル面談はここからカジュアルに応募することができる、お気軽に、応募、是非。

sites.google.com

まず、私は車はHONDA党(インテグラの96スペックTYPE-Rに乗っていた、当時Rはプログラミング言語じゃなくてRacing SpilitsのRだった)なのだが、トヨタイムズのファンでもあるので、冷やかしではなく本当にTRI-ADに興味がある人材です。

これとか最高に面白いから見て欲しい

・・・さて、服部圭悟さんは(最近ちょいちょい色んな人からディスられる)MLOpsやData Engineerをやってる人だ。 私の中ではUberのミケランジェロに異常なライバル心を燃やす機械学習プラットフォーム作りたがってるおじさんという認識だ。 彼は私と同僚だった時、国際的な政治事情に巻き込まれてチームごと爆散してしまったという悲しい過去を持つ男だ。でも、俺はそういう過去を背負った男、嫌いじゃないぜ。 しかし、今はラザロの如く蘇っている。素晴らしい。

ラザロとミケランジェロ、なんか語感が似てる。え?そういうこと?

トライアドって読んでたけどティーアールアイエーディーらしい知ったかぶりしてごめんなさい。 そして2021年からはウーブン・プラネット・ホールディングス株式会社になるらしい

質問とそれに対する返答

質問:ユー、今なにやっとん?

  • TRI(シリコンバレーにあるToyota Research Institute)と協力して機械学習基盤を作ってるよ
  • 自動車なんで莫大なセンサーデータを扱っています
  • 機械学習モデルとそのパイプラインつくるところまでがMISSION
  • 事故が起きた場合、機械学習モデルの説明性が求められるのでそこを頑張っています
    • 今までも自動車のアーキテクチャを15年間保存してきた
    • これらの情報整理(データ・モデルのVersion管理など)とシステム化もチームのMISSION
  • 俺はミケランジェロを作りたい!(アッハイ)

質問:大企業で自動運転ってやれるのですかね?まだまだトライアンドエラーの塊ぽい印象だから失敗を許容して高速改善回さないといけないだろうが、大企業という”型”が足かせになりそう

  • 割とやれる。
  • 今はリリースにとても時間がかかる(とても時間がかかる、大事なこと(ry )ので、リリースプロセスの最適化をやっている。

質問:顧客はどちらさまなのですか?ずっと研究開発してるだけでいいの?

  • 社内とトヨタグループ。個人的にはもっと広げていきたいと思っている。

質問:言うて、TOYOTA系列の自動運転研究開発してるぽいところたくさんあるし、系列で仕事被りまくってんじゃないの?

  • 意外と機械学習のタスクとしては被ってない(へぇ〜!!!)

質問:御チームの構成どうなっとんのや

  • MLOpsで3チームあるよ、そのうちの1つをKeigo HattoriがLead(4人)
  • 残りの2チーム
    • アノテーションチーム(各ベンダーに自動でタスクを投げる(1人))
    • 組み込み系チーム(ハードウェアへのデプロイ系とか(2人))
  • 大増員中です!!!(入社チャンス!!!)
  • 機械学習つよつよ人材はいるので、ソフトウェアつよつよ人材がほしい
  • チームに日本人が多いので日本語も使う。公用語は英語、ドキュメントも英語。

質問:R&D、不況に弱い印象だし、会社ちゃんともつ?爆散はないの?

その他

  • 車専用のOSも作っているよ
  • 会社の文化的にはAmazonから来ている人が多いのでそれっぽい
  • 1年後にTeslaにおいつけおいこせ!