Делаем 10-и минутную задачу за 2 часа с помощью ChatGPT
Все мы видели много статей, где с помощью AI-инструментов за минуты выполняется работа, на которую раньше мог легко уйти день. Особенно впечатляют примеры, где работа (успешно) идет вне зоны компетенции человека (т.е. когда AI позволяет делать то, что человек в принципе один сделать не мог бы). Но сегодня у меня получился несколько другой случай:
Задача стояла относительно тривиальная: нужно было сгенерировать look-up table (LUT) для по-канального увеличения контраста изображения (S-кривая), и применить эту кривую на фотографии с USB-камеры микроскопа. Берем ChatGPT (даже 3.5 должен прокатить, тут всего-то одна формула), за 10 секунд получаем Python-код типичной S-кривой (также ChatGPT для моего удобства сразу добавил отрисовку графика через matplotlib), настраиваем параметр «кривизны» кривой по вкусу — и можно вставлять в финальное приложение. Среди множества S-кривых/сигмоид ChatGPT использовал «логистическую функцию» — она одна из наиболее простых. Но у неё есть и недостаток — параметром «кривизны» нельзя сменить увеличение контраста на снижение (и наоборот) — впрочем, мне это особо и не нужно было, снижать контраст в целевом применении не нужно никогда (крайний случай — оставить все как есть, а это логистическая функция может).
Но праздновать завершение работы оказалось рано: сгенерированный код упрямо рисовал график, снижающий контраст вместо его увеличения. Когда я предъявил эту претензию ChatGPT — он извинился, и с каждым разом выдавал все более и более поломанный код. Просто изменить параметр кривой оказалось невозможно т.к. формула недостаточно универсальная. Что-ж, похоже тут с ChatGPT ничего не получится -, но это и не конец света. В конечном итоге, границы применимости LLM известны, и чем более узкая сфера — тем проще за них выйти. Но история тут не заканчивается.
Графики вариантов S-кривых для увеличения контраста
Для справки, вот исходный (наилучший) вариант от ChatGPT (можно потренироваться и найти тут ошибки):
import numpy as np
import matplotlib.pyplot as plt
def create_s_curve_lut():
# Define parameters for the sigmoid curve
a = 10.0 # Adjust this parameter to control the curve's shape
b = 127.5 # Midpoint of the curve (127.5 for 8-bit grayscale)
# Create the S-curve LUT using the sigmoid function
lut = np.arange(256)
lut = 255 / (1 + np.exp(-a * (lut - b) / 255))
# Normalize the LUT to the 0-255 range
lut = (lut - np.min(lut)) / (np.max(lut) - np.min(lut)) * 255
return lut.astype(np.uint8)
# Create the S-curve LUT
s_curve_lut = create_s_curve_lut()
# Plot the S-curve for visualization
plt.plot(s_curve_lut, range(256))
plt.xlabel("Input Values (0-255)")
plt.ylabel("Output Values (0-255)")
plt.title("S-curve Contrast Enhancement LUT")
plt.show()
# You can access the S-curve LUT with s_curve_lut
Не сумев добится нужной формы кривой я поставил крест на LUT от ChatGPT и переделал (если так можно говорить про пару строчек кода из google) используя регуляризованную неполную бета-функцию. Покрутил параметры a=b пока не получил на графике кривую по вкусу (аналогично тому, что выставляю обычно руками в графическом редакторе) и наконец применил LUT на тестовое изображение используя в OpenCV. К моему полному удивлению, функция уменьшила контраст, а не увеличила его. Как так?
Медитирование не позволило быстро найти ошибку. Я написал тестовый кусочно-линейный LUT для увеличения контраста — и на изображении он дал ожидаемый результат. Лишь добавив кусочно-линейный LUT на график — корень проблемы стал виден: когда я выкинул функцию генерации S-curve LUT от ChatGPT, я оставил код рисования графика. Там ChatGPT старательно напечатал заголовок графика и названия осей…, но сделал конкретную подставу используя данные оси X — в Y, и наоборот. Т.к. параметры plt.plot используются без имени — не используя регулярно / не помня порядок параметров человеку очень легко тут не заметить ошибку.
Таким образом, когда я настраивал фактор формы кривой — я настраивал его на перевернутом графике, и сделал его уменьшающим контраст. Когда я обвинял ChatGPT в том, что он неправильную формулу мне сделал — и он соглашаясь со мной судорожно пытался что-то исправить — у него не было шансов. Т.к. формула была правильная, а ошибка была в отображении графика. Конечно, если ChatGPT указать на ошибку в выводе графика — он радостно соглашается и исправляет её (но так я и сам могу). Это подстава уровня #define true false на компьютерах общего пользования.
Помню, у моего преподавателя по аналитической геометрии был финальный босс на последнем экзамене: когда рассказываешь ему доказательство, он внезапно мог не согласится с одним из шагов и сказать что он неправильный, и чтобы получить 5 — нужно было не запаниковав — доказать верность своего решения. Надеюсь, когда-нибудь языковые модели смогут чаще не соглашаться с пользователем, который не всегда прав.
Посмотреть код ошибки
plt.plot(s_curve_lut, range(256)) #It's a TRAP!
plt.plot(range(256), s_curve_lut) #This is correct
Но и это оказался не конец: Взглянув на график — бросается в глаза легкая асимметричность кривой GPT-TRAP в районе 255. Это банальная ошибка округления : вычисленное вещественное число результата просто приводят к uint8 (=отбрасывают дробную часть), а значит результат (и яркость изображения) будет в среднем на 0.5 единицы / 0.25% меньше, а пиксели с полной яркостью (255) будут намного более редкими. Интересной эту ошибку делает то, что она есть в коде, сгенерированной и ChatGPT, и Bing Copilot и Google Bard. Т.е. видимо такой код очень распространен в тренировочных данных и все модели решили что «умножить на 255 и привести к uint8» — это успех. Технически конечно в диапазон мы попали, но результат не безупречен.
Посмотреть код ошибки
#Rounding error, fractional part is discarded
lut = (lut - np.min(lut)) / (np.max(lut) - np.min(lut)) * 255
#This is correct
lut = np.round((lut - np.min(lut)) / (np.max(lut) - np.min(lut)) * 255)
Выводы:
Языковые модели — как junor разработчики: они могут и будут делать неожиданные ошибки, им нужны четкие инструкции и руководство. Впрочем, разница в том, что джуны вырастут, а у моделей остается ждать следующих поколений. Как и с джунами — с моделями нужно иметь реалистичные ожидания.
Весь код от языковых моделей нужно проверять, никому верить нельзя. И чем менее истоптанная поляна, тем тщательнее нужно проверять. Языковые модели генерируют код очень похожий на правду, а значит ошибки могут быть очень дороги в исправлении.
Если получаем неожиданный результат — зачастую проще спросить несколько разных моделей и сравнить результаты : сейчас в дополнение к ChatGPT (3.5/4) есть Copilot, Bard, Replit и другие. Никто из них не выдал идеальный код с первой попытки, но по этому пути мне не пришлось бы залипать с графиком.
Некоторые ошибки — могут быть системными для многих языковых моделей, видимо унаследованные из частично пересекающегося набора данных для обучения, где эти ошибки были широко представлены (т.к. языковые модели на текущий момент доверяют примерам для обучения безусловно, в отличие от людей). Т.е. модели пока не могут превзойти по качеству данные для обучения. Непонятно, сколько работы еще предстоит чтобы «превзойти учителя». Может так оказаться, что это тот случай когда 10% работы занимают 90% времени. Но прогресс в последние 3 года движется стремительно, возможны и сюрпризы.