見出し画像

巷にあふれる情報を一括管理したい!

いつもM研テックブログをご覧いただきまして、ありがとうございます。朝日新聞社メディア研究開発センター(M研)の田森です。

このテックブログも全体で100号目、私も勤続20年目となりました。弊社でR&D活動を初めてちょうど10年、私にとっては切りのいい数字が並びます。いまではこのテックブログも、弊社のR&Dもいろいろと成果も出てきています。振り返ると、まさに「connecting the dots」の世界だと思っています。

今後も、メディアにおけるR&Dの活動とはどうあるべきか、メディア内のR&Dに携わる技術者はいかにあるべきか、M研のメンバーや弊社に所属するすべての技術者と一緒に考え続けて行きたいと思います。引き続きよろしくお願いします。


さて、現在の私の立場としては7〜8割を管理業務に当てつつ、残りの時間で実際に手を動かしたり、M研メンバーと一緒に方針を考えるような時間を送っています。1つを深掘りすることはないけど、最近の動向は広く浅く知っておかなければいけない立場、何かあれば実験的なコードも書くような「Staff Engineer」に近い立場だと認識しています。何かとまとまった時間がない中で、「若いみんな」についていくにはどうしたらいいか、今回は私の工夫について少しお話できればと思います。ですから、専門性の高い立場の方向けと言うよりは、もう少し幅広な活動をされる方向けかもしれません。お役に立てば幸いです。

前回は論文の探し方をテーマとした内容を書きましたが、今回はもう少し全般的なお話ができればと思っています。

利用するツール

今回は「スキマ時間をうまく利用して情報収集したい」みたいな感じにしたく、基本的に情報を一箇所に集めたい、と思っています。いろいろ試した結果、今回はInoreaderをご紹介します。

これはいわゆる「RSSリーダー」なのですが、RSSの無いページでも更新があると記事を登録してくれる機能があったり、専用のメールアドレスが発行され、そのアドレス宛てに送るとメール本文が記事として登録されたりと、なかなか使い勝手が良いものとなっています。このアプリで一元管理することを目標にしたいと思います。

Inoreaderは有償のコースと無償のコースがありますが、有償だとワンタッチで翻訳できたり、重複する記事を削除できたりするので、結構便利です。本格的な利用には有償利用をおすすめします。

情報ソース

次に、どこの記事をこのInoreaderに登録していこうか、ということですが、

こちらの記事が大変参考になりました。ここで紹介されている記事を一通り見て、自分に必要そうな記事を取捨選択しながらInoreaderに登録していきました。おそらく、紹介されているところを全部登録すると見切れない可能性もあるので、最初は少なめが良いかもしれません。

Inoreaderと各情報ソースの連係

先の記事で紹介されている情報ソースは大別して

  • Webページ

  • メーリングリスト

  • 論文: 「labml.ai」を利用

になります。このうち、Webページやメーリングリストは、Inoreaderで簡単に登録できます。特にメーリングリストは、先に述べたような発行されたメールアドレスで登録すれば、自動的に記事化してくれます。

ニュースレターを追加する機能

一方で、論文はどう管理したらいいでしょうか。先の記事で紹介されているlabml.ai

は、X(Twitter)にてリツイートされたりお気に入り登録された論文をランキング形式にしてまとめてくれています。おそらくこれさえ見ておけばトレンドはつかめるのでは、と思います。それでも全てを見ていくわけにもいきません。理想としては、目標としたInoreaderでざっと眺めつつ「必要なものだけを」「どう連係するか」を少し考えなければなりませんでした。

labml.aiとの連係

今回ご紹介するlabml.aiは「Papers Bar」というスマホのアプリがありますので、これを毎日ざっと眺めて、「関係ありそう」とピンとくるものをInoreaderに連係して、あとからじっくり読むという運用にします。

Inoreaderもアプリがあるので、外部のアプリからInoreaderへ連係するということもできるのですが、

Papers Barの記事共有画面

これで登録すると、記事一覧には「Machine Learning Papers」というタイトルしか出ないため、整理の上では大変なので少し工夫が必要です。

直接共有した場合のInoreader上での表示

以前の記事でPaperpileを紹介しました。これは論文管理ツールなのですが、今回はこれを活用してみたいと思います。Paperpileには、登録された論文のPDFを、自分のGoogle Driveに保存する機能があります。これを利用して、ざっと以下のフローを作りました。

  1. Papers Bar(labml.ai)を眺めて、気になる記事があればその論文のarXivに飛んで、Paperpileに登録する。裏では、Google DriveにPDFが保存される

  2. Google Driveを監視して、新しくPDFが登録されたらそのPDFのタイトルとアブストラクトを取得する

  3. Inoreaderから発行されているメールアドレスにタイトルとアブストラクト、arXivのリンクを送る

1は、あまり手間ではありませんが、完全手動になってしまっています。labml.aiの論文の詳細画面から、Google Searchのボタンを押して、arXivのリンクを探して、そのリンク先からChromeの拡張機能などを使ってPaperpileに登録します

labml.aiの論文詳細画面 G Searchを押す
arXivの概要画面でPaperpileの拡張機能を起動

では、2以降はどうしたらいいでしょうか。実はプログラムでガシガシ書けば2と3のフローは作れると思います。私は少しズル?をして一部ノーコードツールを使いました。私はmakeというツールを使いました。

https://www.make.com/en

makeはあらかじめ色々なコンポーネントが用意されていて、それをブロックのように組み合わせることで様々な機能を簡単に作ることができます。今回私が作ったフロー(makeではシナリオ、というようです)は下記のようなものです。

makeのシナリオ

詳細は(結構簡単なので)省きますが、少し説明をすると、左から右にフローが流れていき、Google Driveの監視、新たなPDFがあればPDFを取得して「HTTP(これは後で説明します)」に投げて分析、その結果をEmailのコンポーネントに投げてInoreaderにメール送信します。

HTTPというコンポーネントで、PDFのタイトルとアブストラクトを取得するのですが、ここだけは自分でプログラム(RESTなAPI)を作りました。せっかくならChatGPTやDeepLのAPIや、Python、LangChain、FastAPIを用いて、ある程度詳細をまとめて返却するようにしました。かなりの暫定的なコードなので、適宜改変することをおすすめします。

実装については、このあたりを参考にしています。

from fastapi import FastAPI, UploadFile, File
import shutil
import requests
from pdfminer.high_level import extract_text

import openai
import chromadb
import langchain
import os

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.document_loaders import PyPDFLoader
from langchain.prompts import PromptTemplate

app = FastAPI()

os.environ["OPENAI_API_KEY"] = YOUR_OPENAI_KEY

prompt_template = """This is the part of thesis about computer science. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.

# Context
{context}

Question: {question}
Answer in Japanese:"""
PROMPT = PromptTemplate(
    template=prompt_template, input_variables=["context", "question"]
)

@app.post("/")
def get_upload_file(upload_file: UploadFile = File(...)):
    # APIで送られてきたファイルの保存
    path = f'files/{upload_file.filename}'
    with open(path, 'w+b') as buffer:
        shutil.copyfileobj(upload_file.file, buffer)
    
    # まず、abstractをDEEPLで翻訳する(翻訳の性能はChatGPTよりよいかもしれない、ということで)
    # 対象の論文からabstractを取得する
    text = extract_text(path)  
    text = text.replace("\n", "")
    text = text[text.lower().find("abstract"):text.lower().find("introduction")]
    text = text.replace("ABSTRACT", "").replace("Abstract", "")

    # DEEPLによる翻訳
    post_url = "https://api.deepl.com/v2/translate"
    api_key = YOUR_DEEPL_KEY
    source_lang = "EN"
    target_lang = "JA"

    content = {
        "text": text,
        "auth_key": api_key,
        "source_lang": source_lang,
        "target_lang": target_lang
    }

    request = requests.post(post_url, data=content)
    result_translate_abst = request.json()["translations"][0]["text"]  # レスポンスから翻訳済みテキストを取り出す

    # 落合フォーマットに合わせた論文の要約
    # PDFを1ページずつテキストにする
    loader = PyPDFLoader(path)
    pages = loader.load_and_split()
    # OpenAI APIの設定
    openai.api_key = os.getenv("OPENAI_API_KEY")
    llm = ChatOpenAI(temperature=0.8, model_name="gpt-4", request_timeout=120, max_retries=3)

    # PDFの中身をEmbedding化
    embeddings = OpenAIEmbeddings(disallowed_special=())
    vectorstore = Chroma.from_documents(pages, embedding=embeddings)

    chain_type_kwargs = {"prompt": PROMPT}
    qa = RetrievalQA.from_chain_type(llm=llm, retriever=vectorstore.as_retriever(),
                                     chain_type="stuff", chain_type_kwargs=chain_type_kwargs)

    # 落合フォーマットに合わせたプロンプト作成
    query_summary = "What is the main point of this paper?"
    query_great_point = "What are the advantages of this paper over similar papers in the past?"
    query_key = "What is the most important aspect of the methods mentioned in this paper?"
    query_valid = "How did the authors confirm the validity of the methods mentioned in this paper?"
    query_discuss = "What is the author's discussion of the experimental results in the paper?"

    result_summary = qa.run(query_summary)
    result_great_point = qa.run(query_great_point)
    result_key = qa.run(query_key)
    result_valid = qa.run(query_valid)
    result_discuss = qa.run(query_discuss)
    print("end...")

    # APIの返却
    return {"filename": upload_file.filename, "abstract_japanese": result_translate_abst,
            "abstract": text, "summary": result_summary, "great_point": result_great_point,
            "key": result_key, "valid": result_valid, "discuss": result_discuss}

PaperpileはPDFのファイル名を論文のタイトルにしてくれますので、それを含めてアブストラクトの日本語訳とか、色々要点をまとめた内容とかを日本語にして返してくれるAPIになっています。

このAPIの内容をmakeでメールに整形してInoreaderに送るという流れにしております。

Inoreaderとlabml.aiを眺める日々

設定が終われば、あとはスマホやタブレットでスキマ時間に消化していきます。各々はじっくり読まず、見出しだけで判断して、必要そうなのは星マーク(あとで読む)をつけていきます。

記事の一覧(いちおう画像はぼかしています)未読件数を見ると、少し溢れてきてますね。

論文についてもこんな情報が最初からついているので、取捨選択に役立ちます。(ただ、結局気になるので捨てずに読んじゃいますが…)

論文が送られてきた様子。ちょっと失敗している。

まとめ

いままで同じようなことをやってはサボってしまっていましたが、この仕組みであれば今のところ数ヶ月続いています。結局サボっちゃって溜まることもありますが、そういうときは数時間かけて集中すればドカンと減らすこともできます。

古くなっちゃった記事は無理に読まなくてもいいわけで、あまり気をはらずに続けていきたいものです。かの昔、だれかが「必要な情報はいずれ、向こうからやってくる」ということを言ってました。未読が溜まってしまった場合は、それを信じて、そっと既読にしてみるのもいいでしょう。


というわけで記念すべき100号目は情報収集について、私の工夫を述べました。記念すべき号としてはキラキラしてなかったかもしれませんが、何はともあれ、今後ともM研をどうぞよろしくお願いいたします。

(メディア事業本部 メディア研究開発センター 田森秀明)