Агенты ИИ для обработки временных рядов и больших массивов данных

Создание с нуля только с использованием Python и Ollama (без GPU, без API-ключа)

введение

Агенты — это системы искусственного интеллекта, работающие на основе больших языковых моделей (LLM), способные рассуждать о своих целях и предпринимать действия для достижения конечной цели. Он предназначен не только для ответа на запросы, но и для организации серии операций, включая обработку данных (таких как кадры данных и временные ряды). Эта возможность позволяет многим реальным приложениям демократизировать доступ к анализу данных, например, автоматизировать создание отчетов, запросы без кода и поддерживать очистку и обработку данных.

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

  • от естественный язык – Большая языковая модель (LLM) считывает таблицу как строку текста и пытается понять ее на основе своей базы знаний.
  • от Создать и выполнить код – Агент активирует инструменты для обработки набора данных как объекта.

Объединяя мощь обработки естественного языка (NLP) с точностью выполнения кода, агенты ИИ позволяют более широкому кругу пользователей взаимодействовать со сложными наборами данных и извлекать ценную информацию.

В этом уроке я продемонстрирую, как работать с датафреймами и временными рядами с помощью агентов искусственного интеллекта . Я приведу несколько полезных фрагментов кода на Python, которые легко можно применить в других подобных случаях (просто скопируйте, вставьте и запустите), и объясню каждую строку кода комментариями, чтобы вы могли воспроизвести этот пример (ссылка на полный код в конце статьи).

 

подготовка

Давайте начнем подготовку Оллама (pip install ollama==0.4.7), библиотека, которая позволяет пользователям локально запускать большие языковые модели с открытым исходным кодом, без необходимости использования облачных сервисов, предоставляя больший контроль над конфиденциальностью данных и производительностью. Поскольку приложение работает локально, никакие данные чата не покидают ваше устройство.

Сначала вам нужно скачать Ollama с сайта.

Далее, в командной строке вашего устройства используйте команду для загрузки выбранной вами модели обработки больших языков (LLM). Я буду использовать Qwen от Alibaba , поскольку она одновременно умная и легковесная.

После завершения загрузки вы можете переключиться на Python и начать писать код.

import ollama
llm = "qwen2.5"

Давайте попробуем большую языковую модель:

stream = ollama.generate(model=llm, prompt='''what time is it?''', stream=True)
for chunk in stream:
    print(chunk['response'], end='', flush=True)

Временной ряд

Временной ряд — это последовательность точек данных, измеренных за определенный период времени, часто используемая для анализа и прогнозирования. Он позволяет увидеть, как переменные изменяются со временем, и используется для выявления тенденций и сезонных закономерностей. Временные ряды являются мощным инструментом статистического анализа и прогнозирования.

Я создам фиктивный набор данных временного ряда для использования в качестве примера.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

## create data
np.random.seed(1) #<--for reproducibility
length = 30
ts = pd.DataFrame(data=np.random.randint(low=0, high=15, size=length),
                  columns=['y'],
                  index=pd.date_range(start='2023-01-01', freq='MS', periods=length).strftime('%Y-%m'))

## plot
ts.plot(kind="bar", figsize=(10,3), legend=False, color="black").grid(axis='y')

Как правило, наборы данных временных рядов имеют очень простую структуру, где основная переменная является столбцом, а время — индексом.

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

dtf = ts.reset_index().rename(columns={"index":"date"})
dtf.head()

Затем необходимо изменить тип данных с DataFrame на Dictionary.

data = dtf.to_dict(orient='records')
data[0:5]

Наконец, от словаря к текстовой строке.

str_data = "\n".join([str(row) for row in data])
str_data

Теперь, когда у нас есть текстовая строка, её можно вставить в подсказку , которую сможет обработать любая языковая модель. Когда вы вставляете набор данных в подсказку, большая языковая модель (LLM) считывает данные как обычный текст, но при этом может понимать структуру и смысл на основе закономерностей, выявленных во время обучения.

prompt = f'''
Analyze this dataset, it contains monthly sales data of an online retail product:
{str_data}
'''

Мы можем легко начать диалог с помощью большой языковой модели (LLM) . Обратите внимание, что в настоящее время это не агент, поскольку у него нет никаких инструментов; мы просто используем лингвистическую модель. Хотя она не обрабатывает числа, как компьютер, LLM может распознавать названия столбцов, временные закономерности, тренды и выбросы, особенно в небольших наборах данных. Она может имитировать анализ и объяснять результаты, но не будет выполнять точные вычисления самостоятельно, поскольку не выполняет код, как агент.

messages = [{"role":"system", "content":prompt}]

while True:
    ## User
    q = input('🙂 >')
    if q == "quit":
        break
    messages.append( {"role":"user", "content":q} )
   
    ## Model
    agent_res = ollama.chat(model=llm, messages=messages, tools=[])
    res = agent_res["message"]["content"]
   
    ## Response
    print("👽 >", f"\x1b[1;30m{res}\x1b[0m")
    messages.append( {"role":"assistant", "content":res} )

Большая языковая модель (LLM) распознает числа и понимает общий контекст так же, как она могла бы понимать рецепт или строку кода.

Как видите, использование больших языковых моделей (LLM) для анализа временных рядов отлично подходит для быстрого получения информации и проведения дискуссий.

Агент

Большие языковые модели (LLM) отлично подходят для генерации идей и исследования начальных концепций, в то время как агент может выполнять код. Поэтому он может выполнять более сложные задачи, такие как построение графиков, прогнозирование и обнаружение аномалий. Итак, давайте создадим инструменты.

Иногда более эффективным может быть использование конечного результата как инструмента . Например, если агент выполняет несколько действий для получения промежуточных результатов, конечный результат можно рассматривать как инструмент, который объединяет всю эту информацию в связный ответ. Такой подход позволяет добиться большей индивидуализации и контроля над результатами.

def final_answer(text:str) -> str:
    return text

tool_final_answer = {'type':'function', 'function':{
  'name': 'final_answer',
  'description': 'Returns a natural language response to the user',
  'parameters': {'type': 'object',
                'required': ['text'],
                'properties': {'text': {'type':'str', 'description':'natural language response'}}
}}}

final_answer(text="hi")

Затем — инструмент кодирования.

import io
import contextlib

def code_exec(code:str) -> str:
    output = io.StringIO()
    with contextlib.redirect_stdout(output):
        try:
            exec(code)
        except Exception as e:
            print(f"Error: {e}")
    return output.getvalue()

tool_code_exec = {'type':'function', 'function':{
  'name': 'code_exec',
  'description': 'Execute python code. Use always the function print() to get the output.',
  'parameters': {'type': 'object',
                'required': ['code'],
                'properties': {
                    'code': {'type':'str', 'description':'code to execute'},
}}}}

code_exec("from datetime import datetime; print(datetime.now().strftime('%H:%M'))")

Кроме того, я добавлю несколько вспомогательных функций для использования инструмента и для запуска агента.

dic_tools = {"final_answer":final_answer, "code_exec":code_exec}

# Utils
def use_tool(agent_res:dict, dic_tools:dict) -> dict:
    ## use tool
    if "tool_calls" in agent_res["message"].keys():
        for tool in agent_res["message"]["tool_calls"]:
            t_name, t_inputs = tool["function"]["name"], tool["function"]["arguments"]
            if f := dic_tools.get(t_name):
                ### calling tool
                print('🔧 >', f"\x1b[1;31m{t_name} -> Inputs: {t_inputs}\x1b[0m")
                ### tool output
                t_output = f(**tool["function"]["arguments"])
                print(t_output)
                ### final res
                res = t_output
            else:
                print('🤬 >', f"\x1b[1;31m{t_name} -> NotFound\x1b[0m")
    ## don't use tool
    if agent_res['message']['content'] != '':
        res = agent_res["message"]["content"]
        t_name, t_inputs = '', ''
    return {'res':res, 'tool_used':t_name, 'inputs_used':t_inputs}

Когда агент пытается решить задачу, я хочу, чтобы он отслеживал, какие инструменты были использованы, какие входные данные он попробовал и какие результаты получил. Процесс следует останавливать только тогда, когда модель будет готова предоставить окончательный ответ.

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

# Start a chat
messages = [{"role":"system", "content":prompt}]
memory = '''
The dataset already exists and it's called 'dtf', don't create a new one.
'''
while True:
    ## User
    q = input('🙂 >')
    if q == "quit":
        break
    messages.append( {"role":"user", "content":q} )

    ## Memory
    messages.append( {"role":"user", "content":memory} )     
   
    ## Model
    available_tools = {"final_answer":tool_final_answer, "code_exec":tool_code_exec}
    res = run_agent(llm, messages, available_tools)
   
    ## Response
    print("👽 >", f"\x1b[1;30m{res}\x1b[0m")
    messages.append( {"role":"assistant", "content":res} )

Создание сюжета — это то, чего не может сделать одна только большая языковая модель (LLM). Но имейте в виду, что даже если агенты могут генерировать изображения, они не могут их видеть, поскольку движок по сути все равно является языковой моделью. Таким образом, пользователь является единственным, кто визуализирует сюжет.

Агент использует библиотеку statsmodels для обучения модели и прогнозирования данных временных рядов.

Работа с большими фреймами данных

Большие языковые модели (LLM) имеют ограниченный объем памяти, что ограничивает объем информации, которую они могут обрабатывать одновременно. Даже самые сложные модели имеют ограничения по объему (несколько сотен страниц текста). Кроме того, большие языковые модели (LLM) не сохраняют память между сеансами, если не включена система извлечения. На практике для эффективной работы с большими массивами данных разработчики часто используют такие стратегии, как фрагментация, расширенная генерация поиска (RAG), векторные базы данных и обобщение контента перед его передачей в модель.

Давайте создадим большой набор данных для экспериментов.

import random
import string

length = 1000

dtf = pd.DataFrame(data={
    'Id': [''.join(random.choices(string.ascii_letters, k=5)) for _ in range(length)],
    'Age': np.random.randint(low=18, high=80, size=length),
    'Score': np.random.uniform(low=50, high=100, size=length).round(1),
    'Status': np.random.choice(['Active','Inactive','Pending'], size=length)
})

dtf.tail()

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

В Python самый простой способ создания инструмента веб-поиска — это использование популярного приватного браузера. DuckDuckGo (pip install duckduckgo-search==6.3.5). Вы можете использовать исходную библиотеку напрямую или импортировать оболочку. Лангчейн (pip install langchain-community==0.3.17).

from langchain_community.tools import DuckDuckGoSearchResults

def search_web(query:str) -> str:
  return DuckDuckGoSearchResults(backend="news").run(query)

tool_search_web = {'type':'function', 'function':{
  'name': 'search_web',
  'description': 'Search the web',
  'parameters': {'type': 'object',
                'required': ['query'],
                'properties': {
                    'query': {'type':'str', 'description':'the topic or subject to search on the web'},
}}}}

search_web(query="nvidia")

Всего у агента теперь 3 инструмента.

dic_tools = {'final_answer':final_answer,
             'search_web':search_web,
             'code_exec':code_exec}

Поскольку я не могу добавить полный фрейм данных в приглашение, я передам только первые 10 строк, чтобы LLM мог понять общий контекст набора данных. Кроме того, я укажу, где найти полный набор данных.

str_data = "\n".join([str(row) for row in dtf.head(10).to_dict(orient='records')])

prompt = f'''
You are a Data Analyst, you will be given a task to solve as best you can.
You have access to the following tools:
- tool 'final_answer' to return a text response.
- tool 'code_exec' to execute Python code.
- tool 'search_web' to search for information on the internet.

If you use the 'code_exec' tool, remember to always use the function print() to get the output.
The dataset already exists and it's called 'dtf', don't create a new one.

This dataset contains credit score for each customer of the bank. Here's the first rows:
{str_data}
'''

Наконец, мы можем запустить агент.

messages = [{"role":"system", "content":prompt}]
memory = '''
The dataset already exists and it's called 'dtf', don't create a new one.
'''
while True:
    ## User
    q = input('🙂 >')
    if q == "quit":
        break
    messages.append( {"role":"user", "content":q} )

    ## Memory
    messages.append( {"role":"user", "content":memory} )     
   
    ## Model
    available_tools = {"final_answer":tool_final_answer, "code_exec":tool_code_exec, "search_web":tool_search_web}
    res = run_agent(llm, messages, available_tools)
   
    ## Response
    print("👽 >", f"\x1b[1;30m{res}\x1b[0m")
    messages.append( {"role":"assistant", "content":res} )

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

Наконец, мне нужно, чтобы агент собрал воедино всю информацию, полученную из этого чата.

 

Заключение

Эта статья послужила учебным пособием, демонстрирующим, как создавать агентов с нуля для работы с временными рядами и большими фреймами данных . Мы рассмотрели два способа взаимодействия моделей с данными: через естественный язык, когда модель обработки больших языковых данных (LLM) интерпретирует таблицу как ряд, используя свою базу знаний, и путем создания и выполнения кода, используя инструменты для работы с набором данных как с объектом.

Полный код для этой статьи: GitHub

Надеюсь, вам понравилось! Не стесняйтесь обращаться ко мне с вопросами и комментариями или делиться своими интересными проектами.

 

Комментарии закрыты.