ギークな技術者の皆様、こんにちは。メリークリスマス。
リモートワークで運動不足を感じているため、VTuberのリングフィットアドベンチャーのプレイ動画を見て気を紛らわしています、VPoEの河合(@vaaaaanquish)です。
本記事はエムスリーアドベントカレンダー2024の最終日の記事にあたります。
エムスリーアドベントカレンダーには今年もギークな記事が揃っていますので、年末年始の技術研鑽のお供に是非どうぞ。
はじめに
私事なのですが、今年はエンジニアの採用活動を頑張りました。
ギークなファンを増やすという観点では、@m3_engineeringのフォロワーがめでたく1万人を超えました。やったー!🎉
その活動の一環として定期的に難読クイズを作成、発信していたのですが、結果として今や社内に100以上のプログラミングクイズが存在しているらしいです(笑)。 その一部は、技術カンファレンスや技術書典でも公開しており、目にした事がある方も少なくないかなと思います。
他にも、エムスリーを紹介するコンテンツとして、200ページ超えの全チーム紹介スライドを作成しました。 エムスリーの大きな企業感と、各チームのスタートアップ感の両方が伝わるよう作っているので、こちらも年末のお供に是非どうぞ。
実際の採用においては、GAFA出身者を筆頭に、メガベンチャーやスタートアップ、大手SIerから続々とギークなエンジニアが入ってきてくれています。 技術で医療に挑戦する仲間が増えることは、兎にも角にも嬉しい事です。
そういった採用活動の中でも、今年一番の推しコンテンツが『Quineクリアファイル』です。 Quineと呼ばれる、実行すると実際に動作するコードが返ってくるコードをベースに、M3ロゴを表現したノベルティです。 かなり人気のノベルティで、多くの方から「ギークな心意気が伝わる!」「◯◯言語も作って!」と絶賛の声を頂いています。
なんと、謎の力で動くgifバージョンも以下記事内にあったりします。
もらった書類やチラシ等も挟んで使えますし、コードが書いてあるので普段使いの中でもハッカー気分を味わえます。 私も愛用していて、4歳の娘がたまにくれるお手紙や折り紙を大事に挟んでいます。
そんな好調のQuineクリアファイルなのですが、先日技術関連のイベントでクリアファイルを配っていたところ、他社の機械学習系のリサーチエンジニアの方から「素晴らしい!良い発想だし真似したい!でもうちはリサーチ系の会社だからコードが書いてあっても興奮する人が少ないかもな…」という相談を受けました。
「たしかに」
そう感じた、全てのギークでスマートなエンジニアと働きたい私は、急いでスクリプトを書き始めたのでした。
ノベルティの下準備
まず、下準備として機械学習リサーチでは必須のツールであるmatplotlib
を使って文字を描画していきます。
ノベルティには社名やロゴを表記したい訳ですが、やはり機械学習エンジニアやデータサイエンティストが使い慣れたツールで作ってこそだと思うからです。
それに、会社名の出てないノベルティを作っても誰も解釈できないですからね*1。
from PIL import Image, ImageFont, ImageDraw, ImageOps import numpy as np from matplotlib import pyplot as plt from matplotlib.ticker import AutoMinorLocator import matplotlib_fontja message = 'M3' # 描画するテキスト font_size = 2000 # フォントサイズ margin = 200 # 文字周辺を囲うマージン # 一旦大きく描画 font = ImageFont.truetype(matplotlib_fontja.get_font_ttf_path(), font_size) plot_size=len(message)*font_size img_array = np.zeros((plot_size, plot_size, 3), np.uint8) img = Image.fromarray(img_array) draw = ImageDraw.Draw(img) # 文字描画後のサイズを取得してcut text_bbox = draw.textbbox((0,0), message, font=font) img_array = img_array[0:text_bbox[3]+text_bbox[1],0:text_bbox[2]] # 文字描画 img = Image.fromarray(img_array) draw = ImageDraw.Draw(img) draw.text((0, 0), message, font=font, fill=(255, 255, 255, 0)) # marginを付与 img=ImageOps.expand(img, border=margin) # 画像をプロット fig = plt.figure() ax1 = fig.add_subplot(111) ax1.imshow(np.array(img)) ax1.axis("off") plt.show()
これでまずは下準備ができました。
matplotlib_fontja
の前身であるjapanize-matplotlib
にフォントのPathを取得するメソッドを生やしたのは実は私なので、なんとなく感慨深い画像です。
以降は、ここで作ったPIL
のimg
を上手く使っていきます。
digits
機械学習エンジニアやデータサイエンティストが最初に扱うデータセットといえば、sklearn.datasets
でしょう。
中でも画像系の機械学習モデルの処理を学ぶ上で必ず通る、定番中の定番データセットがdigits
です。
from sklearn import datasets digits = datasets.load_digits() plt.imshow(digits.images[0]) for i in range(15): plt.subplot(3,5,i+1) plt.axis("off") plt.title(str(digits.target[i])) plt.imshow(digits.images[i],cmap="gray")
そこで、Image Recognitionの専門家が生き生きしだす、digits
の描画を思い返すような画像を作ってみます。
先のロゴ画像生成スクリプトのfont_size
を極限まで小さくして、画像全体を荒くして生成しなおします。
良い感じですね。
もうほぼdigits
と言って差し支えないでしょう。
ですが、より「あの頃見たdigits
」に近付けるため、生成した画像を0.~16.
の値に変換します。
plt.imshow(np.array(img.convert('L'))//16) plt.show()
完璧に「あの頃見たdigits
」ですね。
あとはこれをTシャツなどに印刷してノベルティとしていけば、「解像度が低すぎてぼやけているのか、懐かしの涙でぼやけているのか、もはやわからない」といった声が聞こえてくる事間違いなしでしょう。
plot
機械学習エンジニアやデータサイエンティストは、画像認識を専門とする方々だけでない事は重々承知しています。 Kaggleなどテーブルデータから入った人にも刺さる、そんなノベルティが必要そうです。
テーブルデータといえばExplanatory Data Analysis (EDA)。 データの可視化をするフリをした画像を生成してみます。 ロゴ画像生成スクリプトで生成した画像をマスクとして、散布図を可視化をしてみます。
from numpy.random import randint size = 5000 mask = np.array(img.convert('L'))==0 data = np.array([(size-x,y) for (x,y) in zip( randint(0,mask.shape[0],size=size), randint(0,mask.shape[1],size=size)) if mask[x,y]]) plt.scatter(data[:,1], data[:,0])
どう見てもplt.scatter
です。
EDAをしすぎて疲れた時、コーヒーを飲むマグカップにplt.scatter
が描かれているイケてる空間を演出する、そんなノベルティが作れそうな画像です。
とはいえ、これだけでは散布図感はまだ薄いです。 私は子どもの頃から「デカいものと光るものと動くものは格好良い」という教えで生きてきているため、動かしてみたいと思います。
from matplotlib.animation import FuncAnimation, PillowWriter size = 5000 frames = 100 min_data = 10 mask = np.array(img.convert('L'))==0 data = np.array([(size-x,y) for (x,y) in zip( randint(0,mask.shape[0],size=size), randint(0,mask.shape[1],size=size)) if mask[x,y]]) fig, ax = plt.subplots() sc = ax.scatter(data[:min_data][:,1], data[:min_data][:,0]) def update(frame): frame=frame+1 sc.set_offsets(np.c_[data[:min_data+(size//frames)*frame][:,1], data[:min_data+(size//frames)*frame][:,0]]) return sc, ani = FuncAnimation(fig, update, frames=frames, interval=50, blit=True) ani.save("m3plot.gif", writer=PillowWriter(fps=10))
動きました。
最高です。
技術イベントのブース等で流せば、目を引く事間違いなしのアニメーションができました。
今回はrandint
で生成しましたが、右肩上がりの分布を作って勢いを表現したりする事が出来るかもしれません。
seabornを使ったりして、オシャレにしても良いかもしれませんね。
採用で関わったギークなエンジニアにメールで送ったりすれば、きっと喜ばれるギフトになるでしょう*2。
wordcloud
機械学習エンジニアやデータサイエンティストには、自然言語処理も重要なスキルです。
自然言語処理の可視化といえば、wordcloud
が筆頭に挙がります。
Pythonのwordcloud
モジュールには、画像でmaskする機能がありますので、そちらを使っていきましょう。
テキストは自然言語処理で頑張って作っても良いのですが、令和といえば大LLM時代。 ChatGPTに以下のようなpromptを投げて単語リストを作ってもらいました。
・エムスリー株式会社のエンジニアリンググループを単語1000個で表現して。重複は許します。単語はCSVでちょうだい。 ・エムスリー株式会社を単語1000個で表現して。重複は許します。単語はCSVでちょうだい。 ・エムスリー株式会社のエンジニアが使う技術を単語1000個で表現して。重複は許します。単語はCSVでちょうだい。
上記3つのpromptで生成したCSVを空白区切りの文字列にして、いざ生成です。
from wordcloud import WordCloud wc = WordCloud( # contour_width=1, max_font_size=400, font_path=matplotlib_fontja.get_font_ttf_path(), background_color="white", mask=np.array(img)) wc.generate(text) _, ax = plt.subplots(figsize=(14,14)) ax.imshow(wc) ax.axis("off") plt.show()
良い感じですね。
随分昔なのですが、私はWordCloudの日本語のテキスト配置の処理を書いていた時期があります。
その頃から、日本語WordCloudには「フォントの関係で英語に比べて隙間が出来やすい」という課題があります。
自社のデザイナーと相談しながら、フォントやmax_font_size
を変更するなどしてノベルティで使える画像を作っていくのが良いのではないかなと思います。
本当に良いフォントが見つかるといいですね*3。
おわりに
というわけで、ノベルティをお題にいろいろな画像を作ってみましたというお話でした。 これで2025年もAI分野のイベントが盛り上がる事間違いなしですね!
さて、クリスマスに向けて進んできたエムスリーアドベントカレンダー2024はいかがでしたでしょうか? エムスリーはエンジニアが若干100名ちょっとながら、ギークでスマートなカルチャーで来年も大きく医療事業にチャレンジしていきます。
皆様の応援あってこそだと思っておりますので、来年も何卒よろしくお願いします!
We are hiring !!
今回はAI分野の中でも画像やテーブルデータ、自然言語処理を意識して記事を書いてみました。 エムスリーでは、それら以外のAI技術の活用はもちろんのこと、医師の9割が会員のプラットフォーム「m3.com」や日本No.1の電子カルテ「デジカル」、病院の待ち時間を0にするDXアプリ「デジスマ」など、多くのプロダクトを開発しています。
一緒にチャレンジしてくれるギークでスマートな仲間を随時募集しています。 新卒も中途もカジュアル面談、インターンで私と握手! 2025年はエムスリーへ!