Slackログから社長の代わりを作る
こんにちは。IVRyでエンジニアをしている町田です。
IVRyではソフトウェアエンジニアとして電話の自動応答機能の開発を担当しつつ、AI担当としてデータを活用した今後のIVRyを支える技術開発を行っています。
さて、昨年末の資金調達を経て、2022年のIVRyは大変充実した1年を過ごしました。
それに伴って、関連業務もかなり増え、各チーム忙しい毎日を送っています。
最もその影響を受けているのは、他でもない社長です。
いつも忙しそうなので、社長の代わりをする何かを作ってあげれないかなと思いました。例えば、Slackで社長の代わりに返信するとか…
IVRyはSlackが盛ん
他のメンバーもたくさん記事にしてくれていますが、弊社はSlackが大変盛んです。
リモートや副業のメンバーもいるなかで、効率的な情報伝達は大変重要です。
IVRyでは基本的にプライベートチャンネルNGで、オープンで活発なメッセージのやり取りが行われています。
また、times
と呼ばれる分報チャンネルも活発です。詳しくはこちらの記事をご参照ください。
日報のように一日一回ではなく、何か考えていることや思ったことがあればすぐに投稿し、それに対してみんなでリアクションしていく文化があります。IVRyでは入社と同時に全員が自分のtimes
を持ち、どんどん投稿していきます。
そんな中、圧倒的に投稿数が多いのが社長です。timesに書いたらすぐにリアクションが飛んできます。
今回は、このtimes関連の社長の発話を元にして、社長っぽいことをいうモデルを作ってみようと思います。
データの取得方法
Slackからのデータの取得については、IVRyエンジニアのボルドーさんの記事にまとまっています。
今回は全社員のtimesチャンネルデータをすべて取得します。
これはconversations.history
を利用することで、特定チャンネルの投稿を取得することで可能です。
conversations.list
を利用して全チャンネルidを取得することで、times関連チャンネルのidを確認することが可能です。
def get_channel_ids(ids, next_cur=None) -> Dict:
logger.info(f"call get_channel_ids, next_cur={next_cur}, texts_size: {len(ids)}")
if next_cur is not None:
time.sleep(int(os.environ.get("INTERVAL_CHANNEL_FETCH", 10)))
result = client.conversations_list(
cursor=next_cur,
exclude_archived=True
)
else:
result = client.conversations_list(
exclude_archived=True
)
ids += result["channels"]
next_cur = result.get("response_metadata", {}).get("next_cursor")
if next_cur is None or len(next_cur) == 0:
return ids
else:
return get_channel_ids(ids, next_cur=next_cur)
def get_all_texts(channel_id, texts, next_cur=None) -> List:
logger.info(f"call get_all_texts, next_cur={next_cur}, texts_size: {len(texts)}")
if next_cur is not None:
time.sleep(10)
result = client.conversations_history(
channel=channel_id,
cursor=next_cur,
)
else:
result = client.conversations_history(
channel=channel_id,
)
texts += result["messages"]
next_cur = result.get("response_metadata", {}).get("next_cursor")
if next_cur is None:
return texts
else:
return get_all_texts(channel_id, texts, next_cur=next_cur)
このようにして発話データを取得したあとは、適切な前処理を行って学習データとして使える形に整形する必要があります。
今回の学習データは、(誰かの発話
, 社長の返答
)というペアである必要があります。
timesは大分カジュアルなチャンネルなので、メンションやスレッドを作っての会話は少なく、チャンネルにそのまま返信するスタイルが多いです。
今回はtimestamp順にソートし、誰かの発話の直後30分以内に社長のコメントがついていた場合に限り、学習データとして採用しました。以下のような形で、誰かの発話とそれに対する社長の反応に絞っています。(サウナの話が多いのもなんかリアル)
発話例
(北欧とかテキトーに泊まれるもんなんだろうか, 北欧は泊まれるよ)
(なにかを堀田に頼まれた, けど、わからんw)
(ゆいるはこんな日でも混んでました, www)
(小瀬さん絶対サーフィンできるから一緒にやりたい!, おれもどうせ暇やから遊ぶで)
結果、対象となるデータは19154ペア集まりました。
モデルの学習
言語モデルとして、rinna社が公開している学習済みGPT2モデルを利用し、これをfinetuningしようと思います。
前処理
まず、先程抽出したデータセットをモデル入力のために少しいじります。
GPT2の入力として、tokenizerのinputとする際、入力発話と応答発話を区別するためにsep_token
を差し込み、モデルへの入力としました。
(src
が誰かの発話
, trg
が社長の発話
です)
メモリと学習速度の都合でmax_length=128
としました。実際、カジュアルなSlackの返答に絞っているので発話自体が短いものが多いです。
max_length = 128
def preprocess_function(examples):
inputs = []
targets = []
for src, trg in zip(examples['src'], examples['trg']):
inputs.append(src + tokenizer.sep_token + trg)
model_inputs = tokenizer(inputs, max_length=max_length, truncation=True, padding='max_length')
return model_inputs
dataset = _dataset.map(preprocess_function, batched=True)
学習
今回のtrainingはgoogle colabのTPUを利用しました。
TPUはすごく高速ですが、界隈の開発スピードが非常に速いため、ドキュメント通りの設定方法だと動かずに躓くことが多いかもしれません。どうせこの書き方もすぐに使えなくなるんだろうなと思いつつ、TPUを使うためのポイントも書いておきます。以下を参考にしています。
https://colab.research.google.com/notebooks/tpu.ipynb
https://huggingface.co/docs/accelerate/concept_guides/training_tpu
まず、TPUの利用に必要なライブラリをインストールします。
!pip install cloud-tpu-client==0.10 torch==1.12.0 https://storage.googleapis.com/tpu-pytorch/wheels/colab/torch_xla-1.12-cp37-cp37m-linux_x86_64.whl tensorboard-plugin-profile
※モデル作成時はこれでOKだったんですが、その後12/10に確認すると失敗してしまいました。
colabのruntime自体のアップデートが原因なようです。回避策として、コマンドパレットから「代替ランタイムバージョンを利用する」を選択することで利用可能になりましたのでご参考まで。
import torch_xla
import torch_xla.core.xla_model as xm
device=xm.xla_device()
print(f"device: {device}")
ここでdevice: xla:1
と出ればOKです。
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir=path/to/output,
evaluation_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
weight_decay=0.01,
save_total_limit=3,
num_train_epochs=5,
tpu_num_cores=8,
save_steps=500
)
TrainingArguments
のtpu_num_cores
とper_device_train_batch_size
を適切に指定する必要があります。
trainingは5epochくらいにしておきました。
まだlossは下がりそうでしたが適当に打ち切りました。
実験
さて、学習ができたか、実際に社長に話を伺ってみましょう。
候補文の生成にはpipeline
を利用すると簡単です。
https://huggingface.co/docs/transformers/main_classes/pipelines
> 奥西みたいなことを言ってほしいんだけどどう?
候補1:まさか、こんなに早くwww
候補2:いや、マジでいや
候補3:ほんまにおもんなーい
候補4:まっちーのプロダクトデザイン力がすごいから、そんなにおもんない
割とうまくいってる気がします。
弊社の社長は興味ないことには大体「おもんない」発言をしていますし、私のプロダクトデザイン力も意図せず褒められました。
他にも聞いてみましょう。
> IVRyが来年もっと成長するにはどうしたらいい?
これはすごい議論したい
なるほど。確かにIVRyはみんなで議論することを大事にしています。
2022年もMission/Vision/Valueを決めるために、全員でかなりの期間議論を重ねました。
これからの成長にも議論が大事ということですね。普通に納得しました。これ本当にいいそう。
> 今のIVRyに足りていないものってなんだと思う?
これは長文が生成されました。
??ってなった時に、おれたちなりに考えられる3つの軸で分析すると、
・IVRyの目的・価値観・背景・目的がぶつかっている箇所を見つけ、それを埋めること
・何をしたいかの目的を明確にする(そのためには、何が必要か?を具体的にする)
・目的として設定できるゴール(ゴール地点)を、徹底的に洗い出して、どこを削るかを明確にする あと、ゴールと設定したゴールに対してやってることとかが、ズレていないかも
最後のほうが生成の最大長を超えてしまって切られてしまっているので不自然な感じがしますが、3つに要点をまとめているところとか、本当にコメントとして返ってきそうです。
IVRyでは「べき論に向けて、一歩ずつ進んでいく」ことをValueとして掲げており、常に目的を明確にして状況を整理していくことを意識していますが、なんかそれっぽいこと言ってて驚きです。
> 来年の意気込みをお願いします
来年は、会社を今年より大きく成長させる。そのために、売上総利益率10%超えという強い意志を120%持って、ひたすら邁進する。
リアル社長によると数値目標が低すぎるということですが、強い意志は伝わりました。
会社を成長させるために、僕も頑張ります。
最後に
今回はtimesというカジュアルなチャンネルだけを対象にしたデータセットですので返答もカジュアルですが、他の真面目なチャンネルも含めれば、それらしくレビューしてくれるかもしれません。
真面目な話をすると、こんなので社長の負荷を下げれるわけは無く、一時的に盛り上がっただけでした。根本的には一緒にIVRyを作っていってくれる仲間がもっと必要です。
この記事を最後まで読んでいただいた皆様は、データ分析の素養・興味があるのではないでしょうか。IVRyでご活躍いただける可能性が非常に高いです。
IVRyはまだ設立して短い会社ですが、他社にはないたくさんのデータが溜まっていっています。
今後のフェーズでは、これらのデータを積極的に活用したく、特に自然言語処理・音声関連エンジニアを募集しています。
こんな会社です。新しいチャレンジをどんどん奨励しています。
ご興味を持っていただいた方は是非、ご連絡ください。
Discussion