LoginSignup
6
3

Gemini-1.5-flashに音声と動画を理解させる(かんたん&すごい)

Last updated at Posted at 2024-05-16

Gemini-1.5-flash

Gemini 1.5の新バージョンであるgemini-1.5-flashの性能を試してみたいと思います!
gemini1.5-flashはコンテキストウィンドウが1Mを誇り、動画や音声を適当に投げても、相当な精度で内容を理解してくれます。

今回のアップデートでどれほどの能力を身につけたのか、早速試してみます。

音声データの認識

gemini-1.5-flashでは、音声データをそのままプロンプトにくっつけて投げることができます。
ただし、鵜呑みにしてファイルをプロンプトにくっつけて投げると、8MBまでの容量制限を受けることになります。
そのため、GCPのCloud Strageにファイルをアップロードして、そのURIをモデルの入力とします。

さくっとStreamlitでアプリを作ってみました。

Gemini_Gijiroku.py
import streamlit as st
from google.cloud import storage
from google.generativeai import GenerativeModel
import base64
import vertexai
from vertexai.generative_models import GenerativeModel, Part, FinishReason
import vertexai.preview.generative_models as generative_models
import time

# GCPの設定 (Cloud Storage, Gemini API)
BUCKET_NAME = 'CloudStrageのバケット名'
SERVICE_ACCOUNT_KEY_FILE = 'サービスアカウントのキーファイルへのパス'
PROJECT_NAME = 'プロジェクト名'
REGION_NAME = 'リージョン'

storage_client = storage.Client.from_service_account_json(SERVICE_ACCOUNT_KEY_FILE)
bucket = storage_client.bucket(BUCKET_NAME)

def generate(audio_file):
  vertexai.init(project=PROJECT_NAME, location=REGION_NAME)
  model = GenerativeModel(
    "gemini-1.5-flash-preview-0514",
  )
  responses = model.generate_content(
      [audio_file, """この音声は、ある会議の音声です。この音声をもとに会議の要約を作成してください。
音声に含まれていない情報をでっち上げたり、冗長になったりしてはいけません。

まず、会議の概要、何が議論されたのか、会議の結論、アクションアイテムを冒頭にまとめて記載してください。
会議の概要には、会議名、日時、参加者が必要です。開催日時などが不明の場合は「不明」と記載してください。
また内容はMECEである必要があります。

その後、会議の流れを記載します。
会議の流れは下記の形式で、それぞれのタイムスタンプごとの話題の概要を記載します。
もしも補足するべき内容があれば、備考に記載してください。表の中はすべて左詰となるようにします。
|タイムスタンプ|話題|備考|
"""],
      generation_config=generation_config,
      safety_settings=safety_settings,
      stream=True,
  )

  return responses

def delete_all_blobs(bucket_name):
    bucket = storage_client.bucket(bucket_name)
    blobs = list(bucket.list_blobs())
    for blob in blobs:
        blob.delete()
    
    st.toast("全てのBlobを削除しました", icon='😃')

safety_settings = {
    generative_models.HarmCategory.HARM_CATEGORY_HATE_SPEECH: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    generative_models.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    generative_models.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    generative_models.HarmCategory.HARM_CATEGORY_HARASSMENT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
}

st.set_page_config(
    page_title="Gemini-1.5-pro / マルチモーダル",
    page_icon="🚀",
    layout="wide",
    initial_sidebar_state="expanded",
)

with st.sidebar:
    st.subheader('Generation config')
    max_output_tokens = st.slider(label="出力トークンサイズ", min_value=1024, max_value=8192, value=8192)
    temperature = st.slider(label="Temperature", min_value=0.0, max_value=1.0, value=1.0)
    top_p = st.slider(label="Top P", min_value=0.0, max_value=1.0, value=0.95)
    st.subheader('Cloud Storage')
    st.button(label="Cloud Strage内のファイルを全て消去", on_click=delete_all_blobs, args=(BUCKET_NAME,))
    
generation_config = {
    "max_output_tokens": max_output_tokens,
    "temperature": temperature,
    "top_p": top_p,
}

st.title('音声ファイルから議事録を作る')

uploaded_file = st.file_uploader("音声ファイルを選択します", type=["mp3", "wav"])

if uploaded_file is not None:
    with st.status("議事録を作成しています...", expanded=True) as status:
        # ステータス表示
        status_text = st.empty()  # ステータスメッセージを表示する場所を確保

        # Cloud Storageにアップロード
        blob = bucket.blob(uploaded_file.name)
        status.write("音声ファイルをアップロードしています...")  # アップロード中であることを表示
        blob.upload_from_file(uploaded_file)
        status.write("議事録を作成しています...")  # アップロード中であることを表示

        # Gemini APIで議事録作成
        gcs_uri = f'gs://{BUCKET_NAME}/{uploaded_file.name}'
        audio1 = Part.from_uri(
            mime_type="audio/wav",
            uri=gcs_uri)

        response = generate(audio1)
        status.update(label="完了しました", expanded=False, state="complete") 

    # 議事録をリアルタイム表示
    minutes_container = st.empty()
    minutes = ""
    for chunk in response: # type: ignore
        minutes += chunk.text
        minutes_container.write(minutes)
        time.sleep(0.05)

動かしてみる

サクッと機能を試したいだけなので、いろいろ汚いコードになってしまいました…
Streamlitで簡単に動かしてみます。

streamlit run Gemini_Gijiroku.py

スクリーンショット 2024-05-16 16.44.46.png
画面が立ち上がりましたので、早速音声ファイルを入力してみます。
今回は下記のnoteで公開されていた、新入社員向けの議事録練習用の会議音声を利用します(そんなのあるんですね…)

結果

スクリーンショット 2024-05-16 16.46.53.png

会議の内容を正確に捉えています。
会議の流れもまとめるように指示していたので、その結果も見てみましょう。

スクリーンショット 2024-05-16 16.47.46.png

完璧ですね。後から会議の模様をキャッチアップすることも簡単です。
ただ音声ファイルをそのまま投げただけでこの精度とは、恐れ入ります。

動画データの認識

同様の手順で動画データもそのままモデルへの入力とすることができます。
Gemini-1.5では、動画中の音声データもそのまま理解してくれるとのことなので、面倒な事前処理は不要です。
先ほどのコードを少し編集し、これも試してみます。

Gemini_Douga.py
import streamlit as st
from google.cloud import storage
from google.generativeai import GenerativeModel
import base64
import vertexai
from vertexai.generative_models import GenerativeModel, Part, FinishReason
import vertexai.preview.generative_models as generative_models
import time

# GCPの設定 (Cloud Storage, Gemini API)
BUCKET_NAME = 'CloudStrageのバケット名'
SERVICE_ACCOUNT_KEY_FILE = 'サービスアカウントのキーファイルへのパス'
PROJECT_NAME = 'プロジェクト名'
REGION_NAME = 'リージョン'

storage_client = storage.Client.from_service_account_json(SERVICE_ACCOUNT_KEY_FILE)
bucket = storage_client.bucket(BUCKET_NAME)

def generate(audio_file):
  vertexai.init(project=PROJECT_NAME, location=REGION_NAME)
  model = GenerativeModel(
    "gemini-1.5-flash-preview-0514",
  )

  contents = [video_file, """この動画は、何かのコンテキストを持っています。この動画をもとに要約を作成してください。
動画に含まれていない情報をでっち上げたり、冗長になったりしてはいけません。

まず、動画の概要、被写体は何か、コンテキストは何かを記載します。
コンテキストが不明な場合は、推測して記載してください。その際は「(推測)」と末尾につけてください。
フォーマットは下記の通りとします:
## 動画の概要
<動画の概要を記載>
### コンテキスト
<コンテキストを記載>

その後、動画の流れを記載します。
動画の流れは下記の形式で、それぞれのタイムスタンプごとの概要を記載します。
もしも補足するべき内容があれば、備考に記載してください。表の中はすべて左詰となるようにします。
### 動画の流れ
|タイムスタンプ|コンテキスト|備考|
"""]
  
  tokens = model.count_tokens(contents)

  responses = model.generate_content(
      contents,
      generation_config=generation_config,
      safety_settings=safety_settings,
      stream=True,
  )

  return [responses, tokens]

def delete_all_blobs(bucket_name):
    bucket = storage_client.bucket(bucket_name)
    blobs = list(bucket.list_blobs())
    for blob in blobs:
        blob.delete()
    
    st.toast("全てのBlobを削除しました", icon='😃')

safety_settings = {
    generative_models.HarmCategory.HARM_CATEGORY_HATE_SPEECH: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    generative_models.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    generative_models.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
    generative_models.HarmCategory.HARM_CATEGORY_HARASSMENT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
}

st.set_page_config(
    page_title="Gemini-1.5-pro / マルチモーダル",
    page_icon="🚀",
    layout="wide",
    initial_sidebar_state="expanded",
)

with st.sidebar:
    st.subheader('Generation config')
    max_output_tokens = st.slider(label="出力トークンサイズ", min_value=1024, max_value=8192, value=8192)
    temperature = st.slider(label="Temperature", min_value=0.0, max_value=1.0, value=1.0)
    top_p = st.slider(label="Top P", min_value=0.0, max_value=1.0, value=0.95)
    st.subheader('Cloud Storage')
    st.button(label="Cloud Strage内のファイルを全て消去", on_click=delete_all_blobs, args=(BUCKET_NAME,))
    
generation_config = {
    "max_output_tokens": max_output_tokens,
    "temperature": temperature,
    "top_p": top_p,
}

st.title('動画ファイルを読み込んで内容を解釈する')

uploaded_file = st.file_uploader("動画ファイルを選択します", type=["mp4"])

if uploaded_file is not None:
    with st.status("動画を分析しています...", expanded=True) as status:
        # ステータス表示
        status_text = st.empty()  # ステータスメッセージを表示する場所を確保

        # Cloud Storageにアップロード
        blob = bucket.blob(uploaded_file.name)
        status.write("動画ファイルをアップロードしています...")  # アップロード中であることを表示
        blob.upload_from_file(uploaded_file)
        status.write("回答を準備しています...")  # アップロード中であることを表示

        # Gemini APIで議事録作成
        gcs_uri = f'gs://{BUCKET_NAME}/{uploaded_file.name}'
        video = Part.from_uri(
            mime_type="video/mp4",
            uri=gcs_uri)

        
        response_datas = generate(video)
        response = response_datas[0]
        tokens = response_datas[1]
        status.update(label=f"完了しました。トークン数:{tokens.total_tokens}", expanded=False, state="complete") 

    # 議事録をリアルタイム表示
    minutes_container = st.empty()
    minutes = ""
    for chunk in response: # type: ignore
        minutes += chunk.text
        minutes_container.write(minutes)
        time.sleep(0.05)

ほとんど何も変わっていませんね。
動画データの解釈のために、プロンプトだけ変更しています。

これもStreamlitで起動してみます。

streamlit run Gemini_Douga.py

スクリーンショット 2024-05-16 17.04.36.png

結果

今回は総務省が公開している、BS・CSの4K・8K放送についての紹介動画を読み込ませてみました。
こういう動画は見る気は起きませんが、みなくても内容がわかれば嬉しいですよね。

今回使ったのは1.4K・8Kの魅力という動画ファイルで、およそ4分の長さがあります。
早速結果を見てみましょう。

スクリーンショット 2024-05-16 17.08.15.png

動画の概要やコンテキストをしっかりと認識してくれています。
また音声データと同様に、動画の中身をタイムスタンプ付きで説明してもらいましたが、こちらも非常に正確でした。

まとめ

Gemini-1.5のマルチモーダル性能は強力でした。
特にファイルをそのままモデルに投げるだけで、中身を理解して返答してくれますので、これは活用の幅が広がりそうです。

コンテキストウィンドウも今後2Mへ拡大されるとのことで、よりマルチモーダルの能力を生かした使い方ができそうですね。

おまけ(GPT4o)

スクリーンショット 2024-05-16 17.13.08.png

ちがうちがうそうじゃない
ちょっと横着してGUIから投げたからかもしれません

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3