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

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

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

はじめに

こんにちは, ホクソエムサポーターの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においつけおいこせ!

法人としての価格設定問題からの、おじさんエンジニアの辛さと賃金の関係性

株式会社ホクソエム常務取締役のタカヤナギ=サンです、主にバックオフィス業務を担当しています。

自分メモに書き溜めていたポエムネタが溜まってきたので少しずつ放出していこうと思い筆をとりました。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?

これは何の話なの?

以前、社のお若い方が技術的に楽しそうな案件を持ってこられて、その価格設定をどうするかについて悩まれておられた時がありました。

その際に社内のSlackにいわゆる”おじさんの小言”のようなものをちらほら書いていたので、それを改めて文章にし、更に「あ、この話は私がちょいちょい感じているおじさんエンジニアの辛さと賃金の話にもつながってくるな」思い、そことも絡めて書いたものになります。

法人としての価格設定問題

既にご存じの方もいるかもしれないですが、弊社は全員が副業として回している会社で21世紀にふさわしい働き方改革ダイバージェンス2020を体現した会社となっております。 なので、OSSを開発するかの如く「うわぁ、この案件技術的に超楽しいナリ〜」と土日を潰してほぼ無料のご奉仕価格で開発しちゃうというのもありといえばあり、”ありよりのあり”になるのですがそれはやっていません。

なので、”ありよりのなし”、です。

「なんでじゃい、楽しければお安く引き受けてもいいんじゃないんかい💢(表現はもっとマイルドで低姿勢)」とお若い方がご本人の謙虚さからくる面もあってそう言われていたのですが、そこを諭す際によく引用させてもらっているのがこの「とんかつ屋の悲劇」という記事です。これを一部引用させていただくと

年金が形を変えた補助金に?  「何十年も変わらない値段と、チェーン店ではありえない品質の高さと格安さ」などとグルメサイトでも称賛されていることが多い。しかし、それを可能にしているのは、すでに減価償却の終わった古い設備、ローンを払い終えた自社店舗、そして年金をもらいながら夫婦で切り盛りしていることなどだ。  ある意味、年金が経営継続への補助金のようになっているわけだ。こうした経営を続けてきた場合、いよいよ世代交代の時期になると若い現役世代にはとても生活をしていけるだけの収入を得ることができない。  「そうなってから、急に値段を大幅に上げるなどはできないし、設備更新などに多額の費用がかかるので、後継者にとっては重荷になるでしょう。」商業関係の支援事業を行う行政職員は、そう話す。先の外食産業の社員も、「夫婦二人で一人分の給与しかなく、それでやっと可能になっているような低価格がウリでは、いくら有名でも、のれん代を出してまで買収する意味はあまりない」と言う。

news.yahoo.co.jp

という話です。

要するに「(ご本人らは年金で食えるので)善意から値上げをしていないのかもしれないが、結局はそれが過度な価格下落競争を生んで、近隣の似たような(この場合だととんかつ屋をやりたいお若い方の)店を強制的に廃業に追い込でいる」という話になるわけです。

これに絡めて「いいかい?俺たちはとんかつ屋の悲劇を起こしてはいけない。技術的に面白い(主にRの)話だからといってもめちゃ低価格で引き受けるのはやめよう!それは巡り巡ってお若いRユーザの芽🌱を摘むことになる(Rが儲からない言語になるのは悲しい😢)」という話をしていたのです。

うんうん、わかる、過度なダンピングよくない、それは強制的に海を赤く染める行為なわけです(しかもそこに流れる赤い血は自分のものではなく人のもの)。

おじさんエンジニアの辛さと賃金

これと同じ話が一個人のおじさんエンジニアの賃金にも当てはまってくるぞと、もうちょっと機械学習っぽくいうと”おじさんエンジニアの辛さと賃金”というトピックベクトルは良い感じに”法人の価格設定問題”空間に射影できるぞと、そういうことです。

え?何が関係あるのかって?ちょっと落ち着いて私の経験を聞いて欲しい。

私自身は社会人歴14年で転職を繰り替えしたりホクソエムったりで、合計6社ほどの経験があるのですが、転職の際に年収を2~300万円ほど下げて転職した経験が2度ほどあります。 その意図は「正直、自分を優秀な技術者と仮定するならば、そのうち成果出して給料もあがるだろうし昇給がだめなら転職/副業すればいいや〜」くらいの楽観論が根底にあります。 その一方で、更にガッツリ年収を下げる(年収300万円くらいになるイメージ)のはいくら副業で稼げてもNGです。

もともとの「法人としての価格設定問題」の考えてに至った原点も実はこのへんにあります。 以前、同僚(私の心の中のインフラクラウド師匠)だった方が

「某D社では技術的に凄いおじさんエンジニアがたくさんいるんだが、彼らは全然給料交渉をしない。そして僕らは彼ら技術的に凄いおじさんエンジニアと給料を比較されるので僕らが交渉しても給料がまるで上がらないんだ。だから転職した。」

という話をしていてこれが原点です、めっちゃ印象的でした(かれこれ4〜5年前です)。 それまでは社会を知らなすぎてこの視点が私にはまるでなかったのです(当時社会人8年目くらい)、 pip install 社会性 が実行された瞬間ですくぅぅ。

もちろん人事(あるいは採用コストを管理されてる方々)の側としては「いや、技術的にめっちゃすごいおじさんエンジニアXさんの年収がY円なのに、それより技術的に優れていない(私から見れば優れているんだけど&色々パクらせてもらった)君(インフラクラウド師匠)の給料を上げる必要なくね?」と主張できるわけです。 いや、人事殿らの意見わかる、実に筋が通っている。その通りや!

この場合、誰が(お若い方の給料を上げるという観点で)悪いのかというと私は技術的に凄いおじさんエンジニア氏らだと考えます。 じゃぁどうすべきなのか?犯人探しや批判は猿🐵でもできるので、私のこの状況における解決策とボヤキを書くと

「技術的に凄いおじさんエンジニアはお若い方のお賃金上昇に迷惑をかけないように、会社の売上を上げる(あるいはコストを下げる)ような成果を出して、ある程度の高給を交渉して取っていく必要がある。 はぁ、おじさんエンジニアは辛いな、技術的なことだけ考えていてぇ」

となるわけです。 技術的に凄いおじさんエンジニア、ヤッテイキたい。

そんなポエムでした。 ・・・次回もまた、見てくれよな!