- Регистрация
- 12.10.2016
- Сообщения
- 130
- Благодарностей
- 162
- Баллы
- 43
Дисклеймер:
Первоначальная статья получилась настолько бомбической, что даже не прошла модерацию. Так что, итогового готового решения не будет, но идеи, изложенные в этом материале могут ОЧЕНЬ облегчить жизнь юным автоматизаторам.
Поехали!
Сколько раз вы выдели крутые проекты для гитхабе, или примеры работы какой-то нужной библиотеки,
написанные для visual studio, но сталкивались с тем, что абсолютно непонятно, как это использовать в зенопостере?
Я вот сталкивался. Да и кроме того, писать код в VS или VSCode в разы приятнее чем в редакторе PM (я пользуюсь 5й версией. может редактор уже и поменялся).
К счастью, почти любой найденный декстоп-проект можно сократить до консольного приложения, выбросив оттуда всё ненужное. (Удалять из проекта ненужное тоже надо уметь к сожалению, но это намного проще чем писать код).
И к счастью, есть такая технология Windows Communiction Foundation (WCF) с помощью которой мы можем писать распределенные приложения: В одном месте "сервер" с логикой, в совершенно другом приложении - "клиент" в котором мы подключаемся к "серверу" и вызываем его открытые методы. Технология эта сетевая, но мы будем использовать её на localhost, чтобы не создавать дополнительных трудностей, которые придется решать.
Начнем с простого примера:
Вот здесь https://alekseygulynin.ru/primer-wcf/ описан пример создания простого калькулятора.
Повторим его почти полностью.
В vs напишем "серверную часть" а в зенопостере "клиент".
Если не установлено - Качаем visual studio 2019 https://visualstudio.microsoft.com/ru/thank-you-downloading-visual-studio/?sku=Community&rel=16
бесплатную community версию.
Создаем консольное приложение
Обязательно под платформу .NetFramwork, версия не особо важна, главное не .NetCore.
И полностью копипастим верхний фрагмент кода из статьи в файл Program.cs
Все что красное - наводим мышью и выбираем нужную подсказку:
Умная студия сама подтянет все зависимости.
От себя добавим только две строчки, чтобы в консоли выводилась инфа о том методе который вызывается.
перепишем немного класс MyService:
Скомпилируем (Сборка - Собрать решение или CTRL + SHIFT + B)
И запустим проект: запускать нужно не из студии, а в в папке "..\bin\Debug" .exe файл, обязательно "от имени администратора"
(у меня C:\Users\evgen\Documents\ZennoLab\12й конкурс\на экспорт\калькулятор\Calc12\bin\Debug\Calc12.exe)
Студию можно пока закрыть, а консольку оставляем:
Теперь у нас по адресу (если ничего не меняли из кода в статье) доступны вызовы методов описанного интерфейса:
Напишем под них "клиента" в PM:
Создаем шаблон, добавляем ссылку из GAC
.
В общем коде добавляем Using и интерфейс:
.
А следом немного своего кода:
Добавляем C#-кубик и пишем там такой код:
Запускаем, и в логе зеннопостера видим следующее:
,
А в нашей открытой консольке:
(я несколько раз запускал шаблон, поэтому здесь большее количество вызовов. Картинке с логом ZP соответствуют только последние две строчки из консоли).
Вся магия в том, что мы отправляем две цифры из Зенопостера, а саму арифметику считает консольное приложение.
В классе Service общего кода мы устанавливаем соединение с нашим сервисом, а в кубике пользуемся этим сервисом.
Соответственно, логика на "сервере" может быть любого уровня сложности. И мы можем вынести её из шаблона ZP и писать код в нормальной среде VisualStudio 2019.
Для закрепления первого этапа, напишем свой CRUD-интерфейс для работы с БД.
У меня локально установлена только БД ПОСТГРЕС, с ней и поработаем c использованием Dapper.
У меня есть некоторая таблица с товарами:
Напишем в VS для работы с ней Интерфейс, реализуем его и напишем шаблон в ZP для работы с этой таблицей:
Делаем всё то же самое, как и раньше:
Создаем консольное приложение в VS, устанавливаем пакеты Dapper и Npgsql через консоль:
Далее создадим класс Item описывающий элемент в таблице Goods (забегая вперед, скажу что я столкнулся с трудностью сериализации данных на стороне ZP,
поэтому пришлось использовать XmlSerializer, и у нас добавились некоторые атрибуты)
Далее опишем вот такой интерфейс:
и реализуем этот интерфейс. Кода получилось много:
То что написано в этом классе, это по сути - просто набор sql-запросов для работы с таблицей Goods из моего примера. Dapper хорош тем, что в результате sql-запроса возвращает нам не таблицу данных, а готовый объект (или коллекцию объектов ) на вызов select. И в Update тоже принимает модель.
Именно этот код будет работать только с таблицей по структуре как на скрине выше и "мапить" её в тот объект Item что мы описали.
Почитать немного про даппер можно здесь:
с той лишь разницей, что у нас отличаются названия столбцов таблицы и у меня connectionString захардкожен прямо в коде класса.
Если что-то непонятно внутри самих методов - на stackoverflow полно примеров по использованию даппера. Использование даппера - не тема статьи. Этот код здесь лишь для того, чтобы показать, что WCF можно использовать с разными технологиями.
В методе Main снова поднимаем сервис:
если что-то не компилируется, не забываем про юзинги:
И про ссылку на System.ServiceModel
Компилируем, запускаем:
Идем в PM и начинаемговнокодить:
1. Сcылки из GAC
2. В общем коде создаем такой же класс и интерфейс как в консольке с Даппер-сервисом:
3. Реализуем подключение к службе:
Немного кривовато получилось, но в примере с калькулятором, у меня была проблема с видимостью сигнатуры методов, и было очень неудобно писать код без подсказок.
Эта непонятная смесь каких-то паттернов решила проблему:
4. Добавляем кубик "свой код":
Мы просто создаем 15 записей в БД со сгенерированными названиями, в описание сохраняем время, а цену просто заполняем каким-то значением.
Запускаем в многопотоке в ZP:
Консолька соощает что вызывается метод Add,
а в БД результат:
Остальные методы тоже работают:
и т.д.
Таким образом, общий алгоритм работы с WCF-службами такой:
Описываем интерфейс =>
поднимаем службу =>
описываем интерфейс на клиенте =>
подключаемся к службе =>
пользуемся.
Калькулятор, Постгрес - это всё конечно интересно, но можно и без всякого WCF обойтись - быстрее и проще реализовать это прямо в PM.
Так что, поищем какой-нибудь действительно интересный и полезный нам проект на просторах интернетов.
Сначала я искал готовые примеры какой-нибудь реализации распознавания текста на картинке, для разгадывания математической капчи, но всё что находил либо просто не работало (не собиралось/не компилировалось), либо не могло разгадать вообще ничего. Да и есть такая тема на форуме с TESSNET OCR.
И, О чудо! У Микрософта есть открытый проект на гитхабе с примерами использования нейросети ML.NET
и даже РУКОВОДСТВО как этим пользоваться!
Качаем проект из репозитория выше (для тех кто не пользовался гитом, вот тут:
есть кнопка скачать зип-архив
И открываем файл TransferLearningTF.sln (должна запуститься студия).
Компилируем, запускаем:
.
(если ошибки какие-то, то возможно нужно проделать пункт 6 этапа установка:
)
если не получилось - ничего страшного, ниже будет подробней про этот этап.
Краткая суть для тех кому лень читать статью по ссылке: "предварительно обученная нейросеть на 10 картинках научилась определять по фотографиям "Еду", "Игрушку" и "Технику".
В консоли мы видим название картинки и то что "нагадала" нам эта нейросеть.
Что ж, попробуем её научить делать что-то действительно полезное, например отличать хот-дог от не хот-дога.
1. Создадим в директории /assets/ выкачанного проекта папку /hotdogs/
2. Скачаем из интернета несколько хотдогов назовем их hotdog1..12 + testhotdog и переименуем то что было в папке /images/ в nothotdog1..12 и переместим их в папку /hotdogs/
3. В этой же папке создадим два файла: tags.tsv и test-tags.tsv со следующим содержимым:
tags
test-tags
4. Повторим пункт 5 этапа "Установка" из руководства
https://docs.microsoft.com/ru-ru/dotnet/machine-learning/tutorials/image-classification#setup
для файлов во вновь созданной папке /hotdogs/.
4.1 в обозревателе VS жмем "отобразить все файлы и папки"
4.2 ПКМ по созданной папке - включить в проект.
4.3 Выделим все файлы, ПКМ-свойства
4.4 Копировать более позднюю версию
5. Меняем в 15-й строке название директории с фотками на hotdogs:
а в 18й строке название файла:
и запускаем:
Удивительно, но нейросеть вполне достойно определяет хотдоги и нехотдоги:
Поработаем немного кувалдой и напильником , чтобы сделать из этого WCF-службу, и чтобы можно было отправлять фотки из ЗенноПостера.
Главная проблема в том, что проект скомпилирован под .NetCore, а в .NetCore нет WCF и скорее всего никогда не будет, т.к. W - значит WINDOWS, а Core - это про кросс-платформенность и открытые исходники.
Так что, делаем следующее:
1. Создаем в студии новый проект под NetFramework.
2. Устанавливаем вот эти пакеты:
Делать это удобнее из диспечера пакетов, чем из командной строки:
3. Копируем папку assets/ с нашими хотдогами из старого проекта в новый
4. Снова Проделываем для нового проекта пункт 5 этапа "установка" из руководства для всех файлов из скопированной папки.
5. Полностью переносим содержимое класса Program из Core проекта в новый (с 11 по 186 строки) вставляем вместо строк (9-14 нового)
6. не забываем про юзинги:
7. Запускаем: ОШИБКА. Нужно выбрать конфигурацию.
Идем в свойства проекта
И выбираем целевую платформу сборки (я выбрал x64)
Запускаем и убедимся, что всё у нас собирается.
Ничего не сломалось - уже успех.
Время напильника:
Сразу после первой открывающейся фигурной скобки файла Progran.cs начинаем кодить:
Создадим класс описывающий ответ сервера:
Класс содержит только свойства "имя" и "число"
И опишем интерфейс:
Здесь только один метод - мы будем отправлять из ZP путь до файла с картинкой, а в ответ нейросеть будет сообщать что она там определила,
и насколько это близко к истине (чем ближе к 1 тем точнее категория того что на фото).
Всё что меньше 0,8 - почти неправда.
То что VS нам подчеркивает красным - наводим мышью и выбираем из подсказки то что там мы забыли (юзинги и ссылку на ServiceModel).
Где-то в глубине скачанного (и нового) проекта есть такой метод:
Именно его мы и скопипастим почти полностью для обработки фотографий при реализации нашего интерфейса. От себя добавим только статический конструктор, чтобы модель у нас обучалась лишь единожды - при запуске консольки.
Реализуем интерфейс:
Я стараюсь максимально ничего не менять, только копипастить.
Во-первых, потому что сам не особо понимал что делаю, когда начинал работать над материалом для статьи,
а во-вторых, чтобы было понятно, что мы используем ЧЕЙ-ТО ЧУЖОЙ ПРОЕКТ ПОД VISUAL STUDIO, некоторый "черный ящик" который не понимаем как работает,
и настраиваем лишь "точку доступа" извне для использования в шаблонах ЗП.
Теперь убираем всё из метода Main, и пишем уже знакомый нам код поднятия службы:
компилируем и запускаем от имени администратора .exe файл
у меня
Как видно по консольке:
у нас обучилась модель, протестились картинки из файла test-tags.tsv и открылась наша служба.
Теперь идем в PM и создаем простенький шаблон: (я буду использовать тот же, где мы подключались к службе dapper)
Идем в общий код. и добавляем в конце:
Описываем интерфейс:
Класс описывающий ответ сервера:
Клиент для нашего сервиса:
И в классе Helper вот такой код:
Далее создаем кубик "свой код" со следующим содержимым:
И запускаем.
У меня на рабочем столе винегрет из разных картинок: всякий мусор, одна голая женщина, специально подготовленные хотдоги,
и ловушки из гамбургеров и сэндвичей, проверить насколько умна нейросеть:
И вот что мы видим в логе:
и то же самое в консольке:
На что стоит обратить внимание:
Наша сеть хоть и научилась на 10 картинках отличать явный треш от хотдогов, но то что внешне похоже на хотдог, она принимает за хотдог.
И хотя гамбургер со значением 0,530654 - можно смело относить к нехотдогам (все что меньше 0.8 в параметре score - это как пальцем в небо).
А вот на фотке burger.jpg со значением 0,9941276 нейросеть почти уверена, что определила хотдог.
Как мы видим, это не так. Здесь нет сосиски -
хотя и очень похоже.
Это была специальная ловушка для нашей нейросети, и она в неё попалась.
кроме того, нейросеть не смогла определить хотдог на желтом фоне: (не знаю почему, наверное потому что булки недостаточно контраcтируют с фоном).
Т.е. при обучении нейросети, нужно предусмотреть и такие варианты хотдогов и нехотдогов. Возможно добавить несколько сэндвичей и гамбургеров и вынести их в отдельную (третью) категорию (указать в TSV-файле имяфото.jpg{tab}имяновойкатегории).
Даже такое малое количество фотографий позволяет нам натренировать нейросеть за считанные минуты и бесплатно решать простые капчи из серии "на какой картинке самолет".
Надо всего-то собрать пару десятков картинок каждой категории(котики, самолеты, яблоки и тд). И повторить то же самое, что мы делали с хотдогами.
А потом отправлять фотки из капчи "на проверку" нашей обученной модели.
При наличии большого количества фотографий, можно обучить модель на очень достойные результаты.
Первоначальная статья получилась настолько бомбической, что даже не прошла модерацию. Так что, итогового готового решения не будет, но идеи, изложенные в этом материале могут ОЧЕНЬ облегчить жизнь юным автоматизаторам.
Поехали!
Сколько раз вы выдели крутые проекты для гитхабе, или примеры работы какой-то нужной библиотеки,
написанные для visual studio, но сталкивались с тем, что абсолютно непонятно, как это использовать в зенопостере?
Я вот сталкивался. Да и кроме того, писать код в VS или VSCode в разы приятнее чем в редакторе PM (я пользуюсь 5й версией. может редактор уже и поменялся).
К счастью, почти любой найденный декстоп-проект можно сократить до консольного приложения, выбросив оттуда всё ненужное. (Удалять из проекта ненужное тоже надо уметь к сожалению, но это намного проще чем писать код).
И к счастью, есть такая технология Windows Communiction Foundation (WCF) с помощью которой мы можем писать распределенные приложения: В одном месте "сервер" с логикой, в совершенно другом приложении - "клиент" в котором мы подключаемся к "серверу" и вызываем его открытые методы. Технология эта сетевая, но мы будем использовать её на localhost, чтобы не создавать дополнительных трудностей, которые придется решать.
Начнем с простого примера:
Вот здесь https://alekseygulynin.ru/primer-wcf/ описан пример создания простого калькулятора.
Повторим его почти полностью.
В vs напишем "серверную часть" а в зенопостере "клиент".
Если не установлено - Качаем visual studio 2019 https://visualstudio.microsoft.com/ru/thank-you-downloading-visual-studio/?sku=Community&rel=16
бесплатную community версию.
Создаем консольное приложение
Обязательно под платформу .NetFramwork, версия не особо важна, главное не .NetCore.
И полностью копипастим верхний фрагмент кода из статьи в файл Program.cs
C#:
[ServiceContract]
using System;
using System.Text;
// Обратите внимание, данную библиотеку нужно будет подключить
using System.ServiceModel;
namespace Server
{
[ServiceContract]
public interface IMyService
{
// Далее идут 2 метода, которые будем запрашивать у службы
// Просто опишем их, реализовывать будем в классе
// Сложение
[OperationContract]
double GetSum(double i, double j);
// Умножение
[OperationContract]
double GetMult(double i, double j);
}
// Реализация методов, которые описаны в интерфейсе
public class MyService : IMyService
{
public double GetSum(double i, double j)
{
return i + j;
}
public double GetMult(double i, double j)
{
return i * j;
}
}
class Program
{
static void Main(string[] args)
{
// Инициализируем службу, указываем адрес, по которому она будет доступна
ServiceHost host = new ServiceHost(typeof(MyService), new Uri("http://localhost:8000/MyService"));
// Добавляем конечную точку службы с заданным интерфейсом, привязкой (создаём новую) и адресом конечной точки
host.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "");
// Запускаем службу
host.Open();
Console.WriteLine("Сервер запущен");
Console.ReadLine();
// Закрываем службу
host.Close();
}
}
}
Умная студия сама подтянет все зависимости.
От себя добавим только две строчки, чтобы в консоли выводилась инфа о том методе который вызывается.
перепишем немного класс MyService:
C#:
// Реализация методов, которые описаны в интерфейсе
public class MyService : IMyService
{
public double GetSum(double i, double j)
{
Console.WriteLine($"GetSum {i} + {j}");
return i + j;
}
public double GetMult(double i, double j)
{
Console.WriteLine($"GetMult {i} * {j}");
return i * j;
}
}
И запустим проект: запускать нужно не из студии, а в в папке "..\bin\Debug" .exe файл, обязательно "от имени администратора"
(у меня C:\Users\evgen\Documents\ZennoLab\12й конкурс\на экспорт\калькулятор\Calc12\bin\Debug\Calc12.exe)
Студию можно пока закрыть, а консольку оставляем:
Теперь у нас по адресу (если ничего не меняли из кода в статье) доступны вызовы методов описанного интерфейса:
C#:
double GetSum(double i, double j); //сумма
double GetMult(double i, double j); //умножение
Создаем шаблон, добавляем ссылку из GAC
В общем коде добавляем Using и интерфейс:
А следом немного своего кода:
C#:
public class Service
{
IMyService instance;
public Service()
{
Uri tcpUri = new Uri("http://localhost:8000/MyService");
// Создаём сетевой адрес, с которым клиент будет взаимодействовать
EndpointAddress address = new EndpointAddress(tcpUri);
BasicHttpBinding binding = new BasicHttpBinding();
// Данный класс используется клиентами для отправки сообщений
ChannelFactory<IMyService> factory = new ChannelFactory<IMyService>(binding, address);
// Открываем канал для общения клиента с со службой
instance = factory.CreateChannel();
}
public IMyService GetInstance()
{
return instance;
}
}
C#:
var service = new OwnCode.Service().GetInstance(); //получаем экземпляр сервиса
var summ = service.GetSum(5,4); // вызываем метод СУММА
project.SendInfoToLog("5+4 = "+summ.ToString());
var mult = service.GetMult(11,5); // вызываем метод УМНОЖЕНИЕ
project.SendInfoToLog("11*5 = "+mult.ToString());
А в нашей открытой консольке:
(я несколько раз запускал шаблон, поэтому здесь большее количество вызовов. Картинке с логом ZP соответствуют только последние две строчки из консоли).
Вся магия в том, что мы отправляем две цифры из Зенопостера, а саму арифметику считает консольное приложение.
В классе Service общего кода мы устанавливаем соединение с нашим сервисом, а в кубике пользуемся этим сервисом.
Соответственно, логика на "сервере" может быть любого уровня сложности. И мы можем вынести её из шаблона ZP и писать код в нормальной среде VisualStudio 2019.
Для закрепления первого этапа, напишем свой CRUD-интерфейс для работы с БД.
У меня локально установлена только БД ПОСТГРЕС, с ней и поработаем c использованием Dapper.
У меня есть некоторая таблица с товарами:
Напишем в VS для работы с ней Интерфейс, реализуем его и напишем шаблон в ZP для работы с этой таблицей:
Делаем всё то же самое, как и раньше:
Создаем консольное приложение в VS, устанавливаем пакеты Dapper и Npgsql через консоль:
install-package npgsql
install-package Dapper
Далее создадим класс Item описывающий элемент в таблице Goods (забегая вперед, скажу что я столкнулся с трудностью сериализации данных на стороне ZP,
поэтому пришлось использовать XmlSerializer, и у нас добавились некоторые атрибуты)
C#:
public class Item
{
[XmlElement]
public int Id { get; set; }
[XmlElement]
public string Name { get; set; }
[XmlElement]
public string Description { get; set; }
[XmlElement]
public decimal Price { get; set; }
}
C#:
[ServiceContract]
[XmlSerializerFormat]
public interface IDapperService
{
[OperationContract]
void Add(Item item); //создание
[OperationContract]
void Remove(int id); // удаление
[OperationContract]
void Update(Item item); // обнвление
[OperationContract]
Item FindByID(int id); //чтение 1 записи по ключу
[OperationContract]
Item[] FindAll(); //чтение всех записей
}
C#:
public class DapperService : IDapperService
{
private string connectionString = "User ID=postgres;Password=******;Host=localhost;Port=5433;Database=postgres;";
internal IDbConnection Connection
{
get { return new NpgsqlConnection(connectionString); }
}
public void Add(Item item)
{
Console.WriteLine("Execute method Add");
using (IDbConnection conn = Connection)
{
conn.Open();
string command = "insert into \"Goods\"(\"Name\",\"Description\",\"Price\") ";
command += "values(@Name,@Description,@Price)";
conn.Execute(command, item);
}
}
public Item[] FindAll()
{
Console.WriteLine("Execute method FindAll");
using (IDbConnection conn = Connection)
{
conn.Open();
var result = conn.Query<Item>("select * from \"Goods\"");
return result.ToArray(); //особенности сериализации IEnumerable
}
}
public Item FindByID(int id)
{
Console.WriteLine("Execute method find");
using (IDbConnection conn = Connection)
{
conn.Open();
var res = conn.QueryFirstOrDefault<Item>("select * from \"Goods\" where \"Id\"=@Id LIMIT 1", new { Id = id });
return res;
}
}
public void Remove(int id)
{
Console.WriteLine("Execute method remove");
using (IDbConnection conn = Connection)
{
conn.Open();
conn.Execute("delete from \"Goods\" where id=@Id", id);
}
}
public void Update(Item item)
{
Console.WriteLine("Execute method Update");
using (IDbConnection conn = Connection)
{
conn.Open();
string command = "update \"Goods\" set \"Name\"=@Name,\"Description\"=@Description,\"Price\"=@Price) ";
command += "where \"Id\"=@Id";
conn.Execute(command, item);
}
}
Именно этот код будет работать только с таблицей по структуре как на скрине выше и "мапить" её в тот объект Item что мы описали.
Почитать немного про даппер можно здесь:
Если что-то непонятно внутри самих методов - на stackoverflow полно примеров по использованию даппера. Использование даппера - не тема статьи. Этот код здесь лишь для того, чтобы показать, что WCF можно использовать с разными технологиями.
В методе Main снова поднимаем сервис:
C#:
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(DapperService), new Uri("http://localhost:6000/DapperService"));
host.AddServiceEndpoint(typeof(IDapperService), new BasicHttpBinding(), "");
host.Open();
Console.WriteLine($"Start service {host.Description}, {host.State}");
Console.ReadLine();
host.Close();
Console.WriteLine($"Close service {host.Description}, {host.State}");
Console.ReadLine();
}
C#:
using Dapper;
using Npgsql;
using System;
using System.Data;
using System.Linq;
using System.ServiceModel;
using System.Xml.Serialization;
Компилируем, запускаем:
Идем в PM и начинаем
1. Сcылки из GAC
2. В общем коде создаем такой же класс и интерфейс как в консольке с Даппер-сервисом:
C#:
public class Item
{
[XmlElement]
public int Id { get; set; }
[XmlElement]
public string Name { get; set; }
[XmlElement]
public string Description { get; set; }
[XmlElement]
public decimal Price { get; set; }
}
[ServiceContract]
[XmlSerializerFormat]
public interface IDapperService
{
[OperationContract]
void Add(Item item);
[OperationContract]
void Remove(int id);
[OperationContract]
void Update(Item item);
[OperationContract]
Item FindByID(int id);
[OperationContract]
Item[] FindAll();
}
C#:
public class Helper
{
private static DapperService dapper;
public static DapperService GetDapper()
{
if(dapper==null) dapper = new DapperService();
return dapper;
}
}
public class DapperService
{
public IDapperService service {get;set;}
public ChannelFactory<IDapperService> factory {get;set;}
public DapperService()
{
Uri tcpUri = new Uri("http://localhost:6000/DapperService");
EndpointAddress addr = new EndpointAddress(tcpUri);
BasicHttpBinding binding = new BasicHttpBinding();
factory = new ChannelFactory<IDapperService>(binding, addr);
service = factory.CreateChannel();
}
}
Эта непонятная смесь каких-то паттернов решила проблему:
4. Добавляем кубик "свой код":
C#:
var s = Helper.GetDapper().service; //экземпляр службы
string id = Guid.NewGuid().ToString().Substring(0,8);
for (int i=1;i<15;i++)
{
s.Add(
new Item() {Name=id+"__"+i.ToString(),Description=DateTime.Now.TimeOfDay.ToString(),Price=(decimal)i}
);
}
Запускаем в многопотоке в ZP:
Консолька соощает что вызывается метод Add,
а в БД результат:
Остальные методы тоже работают:
Helper.GetDapper().service.Remove(100); //удалит строку с Id=100
и т.д.
Таким образом, общий алгоритм работы с WCF-службами такой:
Описываем интерфейс =>
поднимаем службу =>
описываем интерфейс на клиенте =>
подключаемся к службе =>
пользуемся.
Калькулятор, Постгрес - это всё конечно интересно, но можно и без всякого WCF обойтись - быстрее и проще реализовать это прямо в PM.
Так что, поищем какой-нибудь действительно интересный и полезный нам проект на просторах интернетов.
Сначала я искал готовые примеры какой-нибудь реализации распознавания текста на картинке, для разгадывания математической капчи, но всё что находил либо просто не работало (не собиралось/не компилировалось), либо не могло разгадать вообще ничего. Да и есть такая тема на форуме с TESSNET OCR.
И, О чудо! У Микрософта есть открытый проект на гитхабе с примерами использования нейросети ML.NET
samples/machine-learning/tutorials at master · dotnet/samples · GitHub
Sample code and snippets used in the .NET documentation - dotnet/samples
github.com
Учебник. Модель классификации ML.NET для категоризации изображений - ML.NET | Microsoft Learn
Узнайте, как обучить модель классификации категоризировать изображения с помощью предварительно обученной модели TensorFlow для обработки изображений.
docs.microsoft.com
Качаем проект из репозитория выше (для тех кто не пользовался гитом, вот тут:
GitHub - dotnet/samples: Sample code and snippets used in the .NET documentation
Sample code and snippets used in the .NET documentation - dotnet/samples
github.com
И открываем файл TransferLearningTF.sln (должна запуститься студия).
Компилируем, запускаем:
.
(если ошибки какие-то, то возможно нужно проделать пункт 6 этапа установка:
Учебник. Создание модели классификации изображений ML.NET на основе предварительно обученной модели TensorFlow - ML.NET | Microsoft Docs
Узнайте, как передавать знания из существующей модели TensorFlow в новую модель классификации изображений ML.NET. Модель TensorFlow была обучена для классификации изображений по тысячам категорий. Модель ML.NET использует передачу обучения, чтобы классифицировать изображения по меньшему...
docs.microsoft.com
если не получилось - ничего страшного, ниже будет подробней про этот этап.
Краткая суть для тех кому лень читать статью по ссылке: "предварительно обученная нейросеть на 10 картинках научилась определять по фотографиям "Еду", "Игрушку" и "Технику".
В консоли мы видим название картинки и то что "нагадала" нам эта нейросеть.
Что ж, попробуем её научить делать что-то действительно полезное, например отличать хот-дог от не хот-дога.
1. Создадим в директории /assets/ выкачанного проекта папку /hotdogs/
2. Скачаем из интернета несколько хотдогов назовем их hotdog1..12 + testhotdog и переименуем то что было в папке /images/ в nothotdog1..12 и переместим их в папку /hotdogs/
3. В этой же папке создадим два файла: tags.tsv и test-tags.tsv со следующим содержимым:
tags
C#:
hotdog1.jpg{TAB}hotdog
итд
hotdog10.jpg{TAB}hotdog
nothotdog1.jpg{TAB}nothotdog
итд до 10
C#:
hotdog11.jpg{TAB}hotdog
hotdog12.jpg{TAB}hotdog
nothotdog11.jpg{TAB}nothotdog
nothotdog12.jpg{TAB}nothotdog
4. Повторим пункт 5 этапа "Установка" из руководства
https://docs.microsoft.com/ru-ru/dotnet/machine-learning/tutorials/image-classification#setup
для файлов во вновь созданной папке /hotdogs/.
4.1 в обозревателе VS жмем "отобразить все файлы и папки"
4.2 ПКМ по созданной папке - включить в проект.
4.3 Выделим все файлы, ПКМ-свойства
4.4 Копировать более позднюю версию
5. Меняем в 15-й строке название директории с фотками на hotdogs:
static readonly string _imagesFolder = Path.Combine(_assetsPath, "hotdogs");
а в 18й строке название файла:
static readonly string _predictSingleImage = Path.Combine(_imagesFolder, "testhotdog.jpg");
и запускаем:
Удивительно, но нейросеть вполне достойно определяет хотдоги и нехотдоги:
Поработаем немного кувалдой и напильником , чтобы сделать из этого WCF-службу, и чтобы можно было отправлять фотки из ЗенноПостера.
Главная проблема в том, что проект скомпилирован под .NetCore, а в .NetCore нет WCF и скорее всего никогда не будет, т.к. W - значит WINDOWS, а Core - это про кросс-платформенность и открытые исходники.
Так что, делаем следующее:
1. Создаем в студии новый проект под NetFramework.
2. Устанавливаем вот эти пакеты:
Делать это удобнее из диспечера пакетов, чем из командной строки:
3. Копируем папку assets/ с нашими хотдогами из старого проекта в новый
4. Снова Проделываем для нового проекта пункт 5 этапа "установка" из руководства для всех файлов из скопированной папки.
5. Полностью переносим содержимое класса Program из Core проекта в новый (с 11 по 186 строки) вставляем вместо строк (9-14 нового)
6. не забываем про юзинги:
C#:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.ML;
using Microsoft.ML.Data;
Идем в свойства проекта
И выбираем целевую платформу сборки (я выбрал x64)
Запускаем и убедимся, что всё у нас собирается.
Ничего не сломалось - уже успех.
Время напильника:
Сразу после первой открывающейся фигурной скобки файла Progran.cs начинаем кодить:
Создадим класс описывающий ответ сервера:
Класс содержит только свойства "имя" и "число"
C#:
public class ImageTarget
{
[XmlElement]
public string Name { get; set; }
[XmlElement]
public float Score { get; set; }
}
C#:
[ServiceContract]
[XmlSerializerFormat]
public interface IFotoService
{
[OperationContract]
ImageTarget CheckFoto(string path);
}
и насколько это близко к истине (чем ближе к 1 тем точнее категория того что на фото).
Всё что меньше 0,8 - почти неправда.
То что VS нам подчеркивает красным - наводим мышью и выбираем из подсказки то что там мы забыли (юзинги и ссылку на ServiceModel).
Где-то в глубине скачанного (и нового) проекта есть такой метод:
public static void ClassifySingleImage(MLContext mlContext, ITransformer model)
Именно его мы и скопипастим почти полностью для обработки фотографий при реализации нашего интерфейса. От себя добавим только статический конструктор, чтобы модель у нас обучалась лишь единожды - при запуске консольки.
Реализуем интерфейс:
C#:
public class FotoService : IFotoService
{
public static MLContext _context;
public static ITransformer _model;
static FotoService()
{
_context = new MLContext();
_model = GenerateModel(_context);
}
public FotoService() { if (_model == null) throw new Exception("Empty model"); }
public ImageTarget CheckFoto(string path)
{
//копипаста:
// load the fully qualified image file name into ImageData
// <SnippetLoadImageData>
var imageData = new ImageData()
{
ImagePath = path
};
// </SnippetLoadImageData>
// <SnippetPredictSingle>
// Make prediction function (input = ImageData, output = ImagePrediction)
var predictor = _context.Model.CreatePredictionEngine<ImageData, ImagePrediction>(_model);
var prediction = predictor.Predict(imageData);
// </SnippetPredictSingle>
Console.WriteLine("=============== Making single image classification ===============");
// <SnippetDisplayPrediction>
Console.WriteLine($"Image: {Path.GetFileName(imageData.ImagePath)} predicted as: {prediction.PredictedLabelValue} with score: {prediction.Score.Max()} ");
//вместо вывода в консоль - возвращаем результат.
return new ImageTarget() { Name = prediction.PredictedLabelValue, Score = prediction.Score.Max() };
// </SnippetDisplayPrediction>
}
}
Во-первых, потому что сам не особо понимал что делаю, когда начинал работать над материалом для статьи,
а во-вторых, чтобы было понятно, что мы используем ЧЕЙ-ТО ЧУЖОЙ ПРОЕКТ ПОД VISUAL STUDIO, некоторый "черный ящик" который не понимаем как работает,
и настраиваем лишь "точку доступа" извне для использования в шаблонах ЗП.
Теперь убираем всё из метода Main, и пишем уже знакомый нам код поднятия службы:
C#:
var service = new FotoService(); // нового здесь только эта строчка
ServiceHost host = new ServiceHost(typeof(FotoService), new Uri("http://localhost:7001/FotoService"));
host.AddServiceEndpoint(typeof(IFotoService), new BasicHttpBinding(), "");
host.Open();
Console.WriteLine($"Start service {host.Description}, {host.State}");
Console.ReadLine();
host.Close();
Console.WriteLine($"Close service {host.Description}, {host.State}");
у меня
C:\Users\evgen\Documents\ZennoLab\12й конкурс\на экспорт\калькулятор\MLNET\MlNet12\bin\Debug\MlNet12.exe
Как видно по консольке:
у нас обучилась модель, протестились картинки из файла test-tags.tsv и открылась наша служба.
Теперь идем в PM и создаем простенький шаблон: (я буду использовать тот же, где мы подключались к службе dapper)
Идем в общий код. и добавляем в конце:
Описываем интерфейс:
C#:
[ServiceContract]
[XmlSerializerFormat]
public interface IFotoService
{
[OperationContract]
ImageTarget CheckFoto(string path);
}
C#:
public class ImageTarget
{
[XmlElement]
public string Name {get;set;}
[XmlElement]
public float Score {get;set;}
}
C#:
public class FotoService
{
public IFotoService service {get;set;}
public ChannelFactory<IFotoService> factory {get;set;}
public FotoService()
{
Uri tcpUri = new Uri("http://localhost:7001/FotoService");
EndpointAddress addr = new EndpointAddress(tcpUri);
BasicHttpBinding binding = new BasicHttpBinding();
factory = new ChannelFactory<IFotoService>(binding, addr);
service = factory.CreateChannel();
}
}
C#:
private static FotoService fs;
public static FotoService GetFotoService()
{
if(fs==null) fs = new FotoService();
return fs;
}
C#:
//отправляем все картинки с рабочего стола нашей нейросети
var fs = Helper.GetFotoService().service;
var list = Directory.GetFiles(@"C:\Users\evgen\Desktop","*.jpg");
foreach (var f in list)
{
var answer = fs.CheckFoto(@f);
var fname = Path.GetFileName(f);
project.SendInfoToLog(
fname+" "+
answer.Name + ":" + answer.Score
);
}
У меня на рабочем столе винегрет из разных картинок: всякий мусор, одна голая женщина, специально подготовленные хотдоги,
и ловушки из гамбургеров и сэндвичей, проверить насколько умна нейросеть:
И вот что мы видим в логе:
и то же самое в консольке:
На что стоит обратить внимание:
Image: bugrer.jpg predicted as: hotdog with score: 0,9941276
Image: gamburger.jpg predicted as: hotdog with score: 0,530654
Наша сеть хоть и научилась на 10 картинках отличать явный треш от хотдогов, но то что внешне похоже на хотдог, она принимает за хотдог.
И хотя гамбургер со значением 0,530654 - можно смело относить к нехотдогам (все что меньше 0.8 в параметре score - это как пальцем в небо).
А вот на фотке burger.jpg со значением 0,9941276 нейросеть почти уверена, что определила хотдог.
Как мы видим, это не так. Здесь нет сосиски -
хотя и очень похоже.
Это была специальная ловушка для нашей нейросети, и она в неё попалась.
кроме того, нейросеть не смогла определить хотдог на желтом фоне: (не знаю почему, наверное потому что булки недостаточно контраcтируют с фоном).
Image: hotdog1.jpg predicted as: nothotdog with score: 0,6148284
Хотя по значению 0,6148284 явно что-то не то с этим результатомТ.е. при обучении нейросети, нужно предусмотреть и такие варианты хотдогов и нехотдогов. Возможно добавить несколько сэндвичей и гамбургеров и вынести их в отдельную (третью) категорию (указать в TSV-файле имяфото.jpg{tab}имяновойкатегории).
Даже такое малое количество фотографий позволяет нам натренировать нейросеть за считанные минуты и бесплатно решать простые капчи из серии "на какой картинке самолет".
Надо всего-то собрать пару десятков картинок каждой категории(котики, самолеты, яблоки и тд). И повторить то же самое, что мы делали с хотдогами.
А потом отправлять фотки из капчи "на проверку" нашей обученной модели.
При наличии большого количества фотографий, можно обучить модель на очень достойные результаты.
- Тема статьи
- Нестандартные хаки
- Номер конкурса статей
- Двенадцатый конкурс статей
Вложения
-
14,3 КБ Просмотры: 256
-
108,6 КБ Просмотры: 263
-
122,3 КБ Просмотры: 290
-
18,9 КБ Просмотры: 267
-
176,7 КБ Просмотры: 275
-
16 КБ Просмотры: 251
-
17,4 КБ Просмотры: 253
Для запуска проектов требуется программа ZennoPoster или ZennoDroid.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...
Для того чтобы запустить шаблон, откройте нужную программу. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.
Последнее редактирование модератором: