Живое демо

В первой половине 2023 года было выпущено много больших языковых моделей, и неясно, что мы можем делать с этими моделями и как они будут использоваться. Мое внимание привлекла одна конкретная модель, которую нелегко понять, поэтому я решил использовать ее в этом путешествии.

Большая языковая модель — это статистический алгоритм, обученный на огромном наборе данных.

Почему?

Этот тип модели извлекает знания из любого типа данных, включая заметки, тексты, базы данных и неструктурированные документы. Он также может классифицировать документы по сходству. Это создает несколько новых бизнес-потоков, что делает его важным.

Что?

Вложение — это способ представления слов или изображений в виде чисел. Это делается путем создания вектора для каждого слова или изображения, где каждый элемент вектора представляет отдельный аспект слова или изображения. Например, один элемент вектора может представлять часть речи слова, а другой элемент может представлять его значение. Слова или изображения, которые семантически похожи, будут иметь похожие векторы. Это можно использовать для таких вещей, как поиск похожих слов или изображений, или для классификации слов или изображений по разным категориям.

Где?

На рынке есть разные модели и с открытым исходным кодом, которые поддерживают этот вариант использования, например T5, Альберт, Универсальный кодировщик предложений (от tf-hub), все-MiniLM-L6-v2 от обнимающего лица или даже text-embedding-ada-002 от OpenAI, мы можем взять один из них и развернуть их ГДЕ-НИБУДЬ, за этим стоят некоторые трудовые/ручные усилия, и все они были созданы из текста, поэтому мы может использовать их только для этой цели.

Я беру модель, сочетающую текст и изображение (называемую мультимодальной), которая уже развернута в Google Cloud, поэтому нам не нужно иметь дело ни с чем.

Решение

Время взяться за руки!

Проект: я создам поисковую систему, способную идентифицировать видеофайлы, выполняя контекстные запросы: "Баскетболист прыгает, чтобы забить"

  1. Взять видео из Google Cloud Storage или любого локального источника (я использовал локальный).
  2. Создавайте кадры в секунду (изображения) из видео и используйте Video Intelligence API для получения расшифровки.
  3. Используйте multimodal-llm, чтобы найти числовое представление (вектор), и вызовите text-llm, чтобы выполнить классификацию (nba или nfl) и суммирование.
  4. Хранить значения в векторной базе данных (я использую cloudsql + pgvector), потому что это просто!
  5. Сделайте контекстный запрос: «Баскетболист прыгает, чтобы забить».
  6. Новое внедренное представление запроса создается.
  7. Сопоставьте номера вложений и принесите дополнительные значения из базы данных (ссылка на видео, ссылка на изображения/fps и т. д.).

Do it

Полный код здесь.

  1. Предварительная обработка видео: мы разбиваем видео на небольшие фрагменты (количество кадров в секунду)/изображения и получаем расшифровку видео (код говорит сам за себя):
  • установите язык (en-US) и функцию (SPEECH_TRANSCRIPTION), кстати, вы можете обнаруживать объекты, метки, сегменты, распознавание/текст и т. д.
  • установить источник видео (облачное хранилище Google) и выход.
  • вызвать API видеоаналитики (vi.AnnotateVideoRequest)
def video_transcription(video_uri):
    print("Transcription job started...")
    file_name = video_uri.split('/')[-1].split('.'[0])
    video_client = vi.VideoIntelligenceServiceClient()
    features = [
        vi.Feature.SPEECH_TRANSCRIPTION,
    ]
    config = vi.SpeechTranscriptionConfig(
        language_code="en-US",
        enable_automatic_punctuation=True,
        enable_speaker_diarization=True
    )
    context = vi.VideoContext(
        speech_transcription_config=config,
    )
    request = vi.AnnotateVideoRequest(
        input_uri=video_uri,
        output_uri=f"gs://vtxdemos-fb-videos-json/{file_name}.json",
        features=features,
        video_context=context,
    )

    print(f'Processing video "{video_uri}"...')
    operation = video_client.annotate_video(request)
    response = cast(vi.AnnotateVideoResponse, operation.result())
    transcript = [n.transcript.strip() for i in response.annotation_results[0].speech_transcriptions for n in i.alternatives][:-2]
    transcript = ",".join(transcript)
    print("Transcription job done")
    return transcript

2. Обобщение и классификация транскрипции: мы берем транскрипцию с последнего шага и просим llm сделать обобщение и классификацию на основе видов спорта (нфл, нба, млс и т. д.).

def llm_sum_class(text):
    vertexai.init(project=project_id, location="us-central1")
    generation_model = TextGenerationModel.from_pretrained("text-bison")

    prompt_1 = """
      You are a sports analyst, provide a brief summary (no more than 15 words) of the following, try to enrich the data with public resources:
      """+text
    summarization = generation_model.predict(
            prompt_1, temperature=0.4, max_output_tokens=1024, top_k=40, top_p=0.8
        ).text
    prompt_2 = f'''Multi-choice problem: What is the category of the following?
    - NBA
    - MLS
    - NFL

    Text: Scores off bench Vela scored one goal to go with one shot 
    The answer is: MLS

    Text: Up a man, Philadelphia managed to find a go-ahead goal in the 124th minute
    The answer is: MLS

    Text: Michael Jordan made a jump shot with 6 seconds left to give the Bulls a 99–98 lead.
    The answer is: NBA

    Text: the Nuggets escaped with a 94-89 win in Game 5 of the 2023 NBA Finals to claim their first championship
    The answer is: NBA

    Text: Dallas Cowboys wide receiver CeeDee Lamb makes a toe-tapping touchdown catch
    The answer is: NFL

    Text:  {summarization}
    The answer is:
    '''
    classification = generation_model.predict(
            prompt_2, temperature=0.4, max_output_tokens=1024, top_k=40, top_p=0.8
        ).text
    print(summarization)
    return summarization, classification

3. Хранить все в векторной базе данных: здесь я использовал google cloud sql, потому что его легко настроить, есть много информации о нем, например эта. Вы также можете использовать Matching Engine, который отличается малой задержкой и масштабируемостью (код).

# Create cloud sql instance
gcloud sql instances create {instance_name} \
  --database-version=POSTGRES_15 \
  --region={region} --cpu=1 --memory=4GB \
  --root-password={database_password}
# Create the database
gcloud sql databases create {database_name} --instance={instance_name}

Все здесь. Но это основная инструкция по созданию БД и вставке чего-либо:

# Create table + schema
await conn.execute(
     """CREATE TABLE video_embeddings(
                     index VARCHAR(100),
                     sports_type VARCHAR(10),
                     summary VARCHAR(1000),
                     frame_link VARCHAR(100),
                     video_link VARCHAR(100),
                     embedding vector(1408))"""
)
await conn.close()
# We take a pandas dataframe from the last step and insert it
for index, row in df2.iterrows():
     await conn.execute(
           "INSERT INTO video_embeddings (index, sports_type, summary, frame_link, video_link, embedding) VALUES ($1, $2, $3, $4, $5, $6)",
           row["index"],
           row["sports_type"],
           row["summary"],
           row["frame_link"],
           row["video_link"],
           row["embedding"],
     )
     await conn.close()

4. Поисковая система: после того, как база данных будет готова, нам понадобится только внешний интерфейс или окно поиска для просмотра контекстных запросов. Я выбрал Streamlit, потому что у меня нет времени заниматься этим (« код").

# Search field where we take the query input

icon("search")
selected = st.text_input("", "A player jumping to score...")
button_clicked = st.button("OK")
text_search = selected
text_search = unidecode(text_search.lower())
##### Query from Database Using LLM and Match with Embeddings

if text_search:
    qe = mm.get_embedding(text=text_search).text_embedding

    vdb = vector_db()
    matches = asyncio.run(vdb.query(qe, database_name="video-frame-emb-2"))  # type: ignore


    # Show the results for similar products that matched the user query.
    matches = pd.DataFrame(matches)
    matches.head(10)
##### Front End Search Engine (cards) using Streamlit

    N_cards_per_row = 3
    if text_search:
        for n_row, row in matches.reset_index().iterrows():
            i = n_row%N_cards_per_row
            if i==0:
                st.write("---")
                cols = st.columns(N_cards_per_row, gap="large")
            # draw the card
            with cols[n_row%N_cards_per_row]:
                st.markdown(f"**{row['sports_type'].strip()}**")
                st.markdown(f"**{row['summary'].strip()}**")
                st.markdown(f"*{row['video_link'].strip()}*")
                link=row['frame_link']
                image_link=row["frame_link"]
                clickable_image = f'<a href="{link}" target="_blank"> <img src="{image_link}" style="width:100%;"> </a>'
                st.markdown("authors_html" + clickable_image, unsafe_allow_html=True)

    st.write(matches["similarity"])

Кстати, я завернул все в dockerfile и запихал в Google Cloud Run (бессерверный контейнерный сервис на базе knative).

Если у вас нет времени читать, просто клонируйте репозиторий, измените файл учетных данных на свои собственные переменные и запустите его:

project_id="YOUR_PROJECT"
database_password="YOUR_DB_CREDENTIALS"
region="us-central1"
instance_name="YOUR_INSTANCE_NAME"
database_user="YOUR_DB_USER"
gcloud run deploy {service_name} --source=. --region=us-central1

И через 4-5 минут у нас будет что-то вроде этого:

Вы можете делать такие запросы, как:

«Уилл Феррел на поле»

«Люди кричат»

Здесь — живое демо (не обещаю сохранить его).

"Подписывайтесь на меня".