Краудсорсинг от Amazon: как полмиллиона людей получают копейки за тренировку ИИ

Интернет-платформы вроде Amazon Mechanical Turk позволяют компаниям разбивать работу на небольшие задачи и предлагать их людям со всего света. Демократизируют ли они работу, или же эксплуатируют беспомощных?

image

Каждое утро после пробуждения Кристи Миланд [Kristy Milland] запускает свой компьютер в Торонто, логинится в Amazon Mechanical Turk и ждёт, когда раздастся сигнал звоночка.

Amazon Mechanical Turk (AMT), существующий уже более 10 лет, это онлайн-платформа, где люди могут за деньги выполнять небольшие задачи. Миланд ищет публикации с предложениями задач – в системе они называются «HIT» – и уведомления сообщают ей, когда задачи соответствуют её критериям. «Уведомления приходят раз в минуту,- говорит Миланд. – Я отрываюсь от своих дел и смотрю, хороший ли это HIT, перед тем, как принять предложение о работе».

Иногда встречаются групповые HIT-задания. «Если подбирается группа, а сейчас обед, или у меня визит к врачу, или надо выгуливать собаку,- говорит Миланд,- я бросаю всё и выполняю задачу. Я привязана к компьютеру. Если только так вы можете прокормить своих детей, уйти у вас не получится». Она занимается этим уже 11 лет.

Миланд – одна из более чем 500 000 «туркеров» – контрактных работников, выполняющих небольшие задачи на цифровой платформе Amazon, которую они зовут mTurk. Количество активных работников, живущих по всему свету, составляет от 15 000 до 20 000 ежемесячно, согласно Паносу Ипеиротису [Panos Ipeirotis], профессору информатики из бизнес-школы Нью-Йоркского университета. Туркеры работают от нескольких минут до 24 часов в день.

Кто такие туркеры? Согласно Ипеиротису, в октябре 2016 года американскими туркерами были в основном женщины. В Индии это в основном мужчины. Если брать всю планету, то их годы рождения будут лежать в промежутке от 1980 до 1990. 75% — американцы, 15-20% — из Индии, 10% — из других стран.

Реквестеры – люди, компании, организации, дающие работу на аутсорс – назначают стоимость каждого задания, и эти задания очень широко варьируются. Среди них встречается:
• Категоризация данных;
• Проставление метатэгов;
• Распознавание символов;
• Ввод данных;
• Сбор емейлов;
• Смысловой анализ;
• Размещение рекламы в видео.

К примеру, одной из недавних задач Миланд была транскрипция содержимого чека. Компания, заказавшая эту работу, продаёт информацию маркетологам и исследовательским отделам из компаний типа Johnson & Johnson, P&G, и т.п. За эту задачу платили 3 цента (около 2 рублей).

Ранние годы AMT

Миланд называет себя жителем цифрового мира. «Половую зрелость я встретила с интернетом», говорит она. Она говорит, что «всегда подрабатывала в интернете», используя платформы типа eBay для получения дополнительного дохода. И когда она наткнулась на статью о возможностях зарабатывать кликами после запуска Amazon Mechanical Turk в 2005 году, это казалось идеальным предложением.


Иконографика (англ) по развитию подобного типа работы (кликабельно)

В ранние годы Миланд казалось, что это больше эксперимент, чем реальная работа. Но во время кризиса 2008-2009 всё поменялось. Миланд, руководившей детским садом, пришлось переехать, и она потеряла доход. В то же время потерял работу и муж. Она начала работать с АМТ на полную ставку. Для неё это означает 17 часов в день ежедневно. «Мы начали смотреть на это, как на работу. И мы начали спрашивать с этого, как с работы».

Рошель Лаплант [Rochelle LaPlante] из Лос-Анджелеса, работает в АМТ полный день с 2012 года. Лаплант соглашается с Миланд в том, что работа непредсказуемая. «Неизвестно, когда опубликуют задачу. Это может быть 3 утра. А в 9 утра вообще нечего делать».

«Я не такая упорная, как некоторые,- говорит Лаплант,- поскольку я ценю сон». Другие, по её словам, ставят уведомления. «Если реквестер публикует запрос в 3 утра, компьютер издаёт сигнал, телефон издаёт сигнал, и они встают и выполняют работу. Они подчинены этому графику».

Ни у Миланд, ни у Лаплант, нет «обычного» рабочего дня. Обычно они задают себе планку по количеству заработанных денег. В нормальный день Лаплант может работать 8 часов. «Но это 10 минут там, 20 минут сям – и всё это накапливается», говорит она.

И сколько же в среднем зарабатывают туркеры? Сложно сказать. Эдриен Жабур [Adrien Jabbour] из Индии говорит, что «можно считать достижением, если ты заработал $700 за два месяца работы по 4-5 часов в день». Миланд говорит, что недавно за 8 часов она заработала $25, и для неё это было «неплохо». По данным Pew Research Center, немногим более половины туркеров зарабатывают меньше уровня минимальной заработной платы, установленного в США — $7,25 в час.

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

Продвинутые туркеры

Грустная реальность работающих в АМТ людей состоит в том, что не все туркеры созданы равными. Система Amazon назначает некоторых работников «специалистами» [Master’s Level]. Когда новый реквестер размещает HIT, система автоматически ищет туркеров такого уровня. Это стоит больше для реквестера и приносит больше денег работникам. Если у вас нет этого уровня, вам достанется меньше работы. В один из будних дней марта, говорит Миланд, в системе было 4 911 задач. Она могла выбирать 393 из них – всего 8%.

А как же получить звание специалиста? Никто не знает. Миланд видела, как неквалифицированные люди – с малым количеством выполненных задач, с малым рейтингом, подставными или приостановленными учётными записями – получали это звание. «В этом не видно никакой системы», говорит она.

Amazon не публикует свои критерии для достижения этого уровня. На форумах туркеров водятся разные теории насчёт получения уровня специалиста. Иногда происходит публикация набора задач, и те, кто успешно справляются с ними, получают этот уровень. «Для этого требуется оказаться в нужном месте в нужное время», говорит Миланд.

Кроме уровня специалиста, существуют и региональные ограничения. Если вы находитесь не в США, это плохо – многие реквестеры ограничивают исполнителей этим регионом.

Зарплата

«Нет двух одинаковых туркеров,- говорит Лаплант. – Некоторые с этого кормятся, другие зарабатывают на карманные расходы».

Уильям Литл – модератор TurkerNation, онлайн-сообщества туркеров из Онтарио. Он получает с АМТ дополнительный доход. Он стремиться заработать $15 в день за три часа работы. «В большинстве случаев это достижимо,- говорит он,- и это лучше, чем заработок в начале карьеры». Но оплата – основная трудность для многих туркеров.

Сейчас деньги получают только туркеры из США и Индии. Другие, включая Миланд и Литла, получают подарочные карты Amazon.

Литл ездит на машине 45 минут до границы с США, где он может получить вещи с Amazon с бесплатной доставкой, и забирает свои покупки. Существуют обходные пути для тех, кто хочет получать зарплату деньгами, но обычно они связаны с уменьшением доходов. Разные веб-сайты, типа purse.io, могут конвертировать подарочные карты, например, в биткоины.

«Вы размещаете свой список желаний на purse.io. Я его вижу, и решаю купить какой-либо предмет. Я его заказываю и отправляю вам,- говорит Литл. – Биткоины хранятся в эскроу. Когда вы получаете покупку, я получаю биткоины». Потом Литл может их продать, получить деньги через PayPal и перевести их в банк. «Я дважды плачу за переводы, и это того не стоит».

Ещё одна проблема – неоплаченный труд. Вашу работу могут отвергнуть без объяснений. Кроме того, туркеры тратят время на оценку работы, ищут репутацию реквестера. Загружают скрипты, добавляют инструменты, проверяют статистику.

Рабы компьютеров

Миланд и Лаплант участвуют в невидимой онлайновой рабочей силе – а именно такая сила всё больше востребована для тренировки умных машин. Умные системы постепенно проникают в повседневный быт, ИИ всё больше используется обществом. Под управлением сегодняшних ограниченных версий ИИ находится всё, от голосовых виртуальных помощников, типа Amazon’s Alexa и Microsoft’s Cortana, до систем компьютерного зрения, лежащих в основе автопилота в автомобилях Tesla.

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

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

Такое обучение требует огромных объёмов данных, некоторым системам требуются миллионы примеров для эффективного выполнения работы. Эти наборы велики, и постоянно растут. Google недавно рассказал о наборе Open Images Dataset с 9 миллионами изображений, а в репозитории YouTube-8M содержится 8 миллионов помеченных видеороликов. В ImageNet, одной из самых ранних баз данных этого типа, содержится более 14 миллионов изображений, разбитых по категориям. Два года её создавало 50 000 человек – большинство из которых было нанято через АМТ. Они проверяли, сортировали, помечали почти миллиард изображений из потенциальных кандидатов.

Из-за масштабов этих наборов данных, даже распределённых среди множества работников, каждому из них приходится повторять одно и то же действие сотни раз. Это работа чёрная, и крайне утомительная умственно.

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

С повальным распространением ИИ каждая технологическая фирма вовлекает людей в выполнение таких микрозадач, связанных с машинным обучением. Amazon, Apple, Facebook, Google, IBM и Microsoft – все крупнейшие технологические компании – либо обладают собственной платформой для краудворкинга, либо отдают эти задачи на аутсорс по контракту внешним компаниям. Из этих компаний крупнейшими являются Amazon Mechanical Turk и CrowdFlower.

Внутренние платформы для микроработы, такие, как Universal Human Relevance System (UHRS) от Microsoft или EWOK от Google, используются довольно активно. Около пяти лет назад, после запуска UHRS, было известно, что платформа используется в поисковике Bing и в других разнообразных проектах Microsoft, обрабатывая при этом 7,5 миллионов задач в месяц.

Согласно Мэри Грэй [Mary Gray], главному исследователю Microsoft, UHRS очень похожа на Amazon Mechanical Turk. Грэй утверждает, что компания использует UHRS для набора работников в тех регионах, где «влияние Amazon Mechanical Turk представлено недостаточно полно», или же в случае чувствительных секретных заданий.

«Каждая компания, заинтересованная в автоматизации услуг, обращается к какому-либо аналогу платформ типа АМТ. И на самом деле многие из них используют непосредственно АМТ», говорит она.

Крис Бишоп [Chris Bishop], директор исследовательской лаборатории Исследовательского центра Microsoft в Кембридже, говорит, что UHRS позволяет компании быть «чуть более гибкой» по сравнению с внешними платформами, например, с той же АМТ. Он говорит, что фирма использует ИИ для автоматического определения сильных и слабых сторон работников, таких, как относительный уровень экспертных знаний, что помогает компании назначать результатам работы этих людей различные оценки важности.

Кроме помощи в тренировке ИИ такие платформы, как АМТ, используются такими известными брендами, как eBay и Autodesk – они сбрасывают повторяющуюся и рутинную работу, которая вот уже много лет составляет большую часть всех заданий в АМТ.

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

Как мы дошли до этого?

В идее о помощи, оказываемой людьми машинам в выполнении задач, которые иначе оказались бы для последних неподъёмными, нет ничего нового. Хотя недавний взлёт ИИ неимоверно увеличил запросы на категоризацию данных, такие микрозадачи, по словам Грэя, встречались ещё лет 20 назад, когда такая работа была связана с попытками улучшить проверку правописания текстовыми процессорами, такими, как Microsoft Word. В более широком смысле работа кликеров и выполнение микрозадач встречались ещё во времена подъёма интернет-магазинов во время пузыря доткомов, в конце 1990-х и начале 2000-х.

В 2001 году Amazon, выискивая новые пути в эффективной организации продуктов в своём быстрорастущем магазине и решения складских проблем, не поддававшихся компьютерам, запатентовала гибридную систему машина/человек. Через четыре года Amazon реализовала свою цель построения цифровой платформы для доступа к большому количеству онлайн-работников, запустив Amazon Mechanical Turk.


Примерное количество активных участников проекта АМТ, с июля 2015 до октября 2016

Идея о возможности доступа к «искусственному искусственному интеллекту», как описала Amazon свой проект, понравилась самым разным компаниям; все они, от интернет-магазинов до порносайтов, искали возможности недорогой сортировки своих продуктов.

В 2015 году ежедневно свои задачи на АМТ размещали в среднем 1278 заказчиков. И хотя количество выполняемой неутомимыми тружениками работы растёт, особенно у таких сайтов, как CrowdFlower, точные её объёмы неизвестны, поскольку довольно большая её часть ведётся без записей или же выдаётся повторно многим работникам.

И хотя, согласно информации с сайта Amazon, на работу в АМТ подписалось уже 500 000 человек, из этих цифр непонятно, как именно люди используют краудворкинговую платформу – как работу на полный день, или как подработку.

Отчёт от Всемирного банка, The Global Opportunity in Online Outsourcing [Мировые возможности в онлайн-аутсорсе], оценивает, что у двух крупнейших платформ для работы с микрозадачами, Amazon Mechanical Turk и CrowdFlower, совместные доходы составили порядка $120 млн в 2013 году. Профессор Вили Ледонвирта [Vili Lehdonvirta], адъюнкт-профессор и главный научный сотрудник в Оксфордском институте интернета предполагает, что эта сумма составляет от 5 до 10% мирового рынка труда, но указывает на трудности в получении реальных цифр занятости для платформ не на английском языке.

Прочая стоимость кликерства

Монотонность такой работы может иметь для выполняющих её людей нехорошие последствия. Она может серьёзно расстроить физическое и моральное здоровье некоторых из них.

«Я просыпаюсь, игнорируя всё остальное,- говорит Миланд. – Моя семья готовит мне еду и оставляет её, чтобы я могла есть во время работы. Я ем за компьютером, не вижу свою семью. Если моей дочери нужна помощь с домашним заданием, ей нужно обращаться к отцу. Дошло до того, что у меня в запястье развилась гигрома. У меня постоянно болели связки в руке. Мне повезло, что я работала так в то время, когда дома был и мой муж, когда у него не было работы. Если домашние слышали звонок с моего компьютера, обозначавший высокооплачиваемую работу, они говорили мне ‘Давай, давай, давай!’ ».

Туркер из южной Индии, Маниш Батия [Manish Bhatia], был модератором-волонтёром на форуме MTurk Forum почти два года, а сейчас модерирует два форума. Самое странное, что его просили сделать – снять себя лежащим в ванне в окружении лепестков роз. «Это было очень странно», говорит он. По поводу странных изображений он также жалуется на то, что иногда ему приходится видеть неприятные картинки. «Заранее ничего неизвестно,- говорит он. – Уклониться от работы можно потом». Но в этом случае вам не заплатят, и время будет потеряно.

Миланд тоже жаловалась на подобный опыт. «Люди говорят мне, ‘Ого, ты работаешь дома? Тебе повезло!’,- говорит она. – Нельзя рассказать им, что я сегодня назначала картинкам тэги, и все картинки были связаны с ИГИЛ. Там была, к примеру, корзина с отрезанными головами. И это я видела совсем недавно. Мне пришлось проставлять тэги к видео с горящим человеком. Платили по 10 центов за фото».

Не только Миланд приходиться ставить тэги к графике или непонятным изображениям. «Вчера на очередном наборе видео с YouTube,- говорит Лаплант,- было очень много обезглавливаний. Внизу есть галочка под названием „неподходящее содержимое“, и ты жмёшь „Отправить“. Это работа может быть важна для предотвращения появления неприятного контента в онлайне, но она же может наносить вред выполняющим её людям. Оплата работы не всегда соответствует ценности проделанной работы для YouTube или его пользователей.

Литл говорит, что ему часто приходилось ставить тэги к порнографическим видео или фотографиям. „И я делал исключения только в случаях, когда встречал детскую порнографию,- говорит Литл. – Я сообщал об этом реквестеру и в Amazon“. Но по поводу кровищи и издевательств Литл говорит, что это „часть работы“.

По завершению задачи невозможно узнать, что происходит с результатом. „Интересно, кто-нибудь будет это просматривать? Надеюсь, что об этом сообщат и удалят,- говорит Лаплант. – Кто-то наткнулся на детскую порнографию, отметил галочку, но кто-либо будет это проверять и расследовать? Мы не знаем“.

Реквестеры работают под псевдонимами, и никто не знает, кто заказал эту работу. Лаплант называет это „диким Западом“. И в то время, как реквестеры назначают туркерам рейтинги, туркеры не имеют возможности рейтинговать реквестеров.

»Ты отмечаешь лица в толпе, но может это кто-то готовит что-то с вредоносной целью, или что-то вроде этого,- говорит она. – Вы не знаете, чем занимаетесь, никакой информации нет".

«Это называется замещающей травматизацией,- говорит Джон Сулер [John Suler], профессор психологии в Университете им. Райдера, специализирующийся на поведении в киберпространстве. – То же происходит с людьми, первыми увидевшими ужасные изображения. Они получают травму». По его словам, мы не всегда понимаем психологические последствия этого. «Наше сознание становится невосприимчивым,- говорит Сулер. – Но подсознание этого не делает – оно всё впитывает. Мы недооцениваем то, как всё, что мы видим в онлайне, влияет на наше подсознание».

Работающие таким образом люди находят онлайн-форумы, чтобы связываться друг с другом и делиться историями, выражать сочувствие и поддерживать друг друга. «Вокруг оплаты и модерации контента возникает очень много вопросов», говорит Миланд. «И в таких местах можно найти социальную поддержку».

У каждой из общественных платформ есть свои особенности. Форум MTurk Forum похож на разговоры у офисного кулера. Напротив, по словам Миланд, Mturkgrind «кажется более сосредоточенным на продуктивности и эффективности». TurkerNation «концентрируется на ответах на вопросы и помощи новичкам в понимании системы».

Есть ещё закрытая группа в Facebook под названием Mturk Members, где уже 4436 членов. Они задают вопросы, хвалятся заработками и поддерживают друг друга.

Лаплант и три другие женщины создали форум MTurk Crowd, чтобы помогать туркерам находить нужные ресурсы и выполнять работу наилучшим образом. Есть ещё много всяких форумов, сабреддитов и других онлайн-платформ.

Существует сайт для работников WeAreDynamo.org. Именно там стартовала кампания "Дорогой Джеф Безос". Кампания пыталась очеловечить туркеров, дать право голоса людям, активно участвующим в жизни этой платформы. Они делились опытом и высказывали опасения по поводу сути своей работы.

Но это мало что поменяло. Хотя у работников из Индии открылась возможность получать зарплату безналичным переводом, ни Amazon, ни Джеф Безос не обращались к кампании напрямую.

Каким-то образом общаться с Amazon практически невозможно. «Отсутствие поддержки раздражает,- говорит Батия. – Нет ни чата, ни телефона». Единственный способ связи – это электронные письма, в ответ на которые приходят стандартные отписки.

«Я весьма озадачена теми решениями, которые они принимают,- говорит Литл,- и первое из решений – недостаток коммуникаций. Почему они не хотят заниматься этим? Вряд ли из-за возможностей исков, потому что их правила работы прямо запрещают такие вещи».

Миланд беседовала с юристами, но «никто из них никогда не поддержит работника в борьбе против Amazon», говорит она. Amazon отказывается общаться. «Ни по поводу отказов, ни по поводу улучшений, ни по поводу наших предложений об увеличении их прибыли – никак».

Лили Ирани [Lilly Irani], преподающая в Калифорнийском университете в Сан-Диего, изучает «культурную политику методов выполнения высокотехнологичной работы». Ирани участвовала в проведении исследования в 2013 году, во время которого учёные подробно изучали форумы туркеров. Исследование проводилось в попытках понять, как могут работать совместные действия – на примере таких проектов, как Dynamo, коллективная платформа туркеров, и Turkopticon, позволяющего туркерам писать обзоры заданий и ставить им оценки. В работе «Turkopticon: прерванная невидимость работников в Amazon Mechanical Turk» авторы отметили: «Мы утверждаем, что АМТ занимается построением инфраструктуры на основе своих работников и прячет их труд, превращая их в вычислительный ресурс для технологов».

Несмотря на плохие условия труда, Миланд и другие люди полагаются на доход от АМТ. Состояние здоровья Миланд не позволяет ей надеяться на традиционную работу. «Я пыталась устроиться в Макдональдс, и меня не взяли», говорит она.

Люди работают вместе с ИИ

Грэй из Microsoft считает, что такого рода трудоустройство по требованию постепенно перерастёт в системы человек+ИИ, в которых появится симбиоз людей и машин.


Таблица задач и оплаты Кристи Миланд. Submitted – выполненные, но ещё не признанные легитимными задания. Approved – оплачиваемые. Rejected – работа была выполнена, но реквестер не принял результаты или не оплатил их.

Она ссылается на появление виртуальных ассистентов, например, Facebook M, или чатботов поддержки, например, Amelia от IPsoft, где люди обрабатывают запросы при помощи ИИ, или ИИ обрабатывает запросы, а человек берёт управление в случаях, разобраться с которыми машине не по силам. Со временем такие системы обучаются на основе ответов людей и постепенно увеличивают спектр обрабатываемых ими запросов.

Появляется всё больше сервисов, использующих специализированный ИИ для выполнения простых задач и людей для более сложных. Один из главных центров краудсорсинга, CrowdFlower, недавно запустил платформу машинного обучения, призванную выполнять задачи, которые ранее решали бы люди. Людям остаётся "сконцентрироваться на более сложных случаях и помогать моделям МО обучаться". Такой подход автоматизирует гору ручной работы, но оптимистичные прогнозы утверждают, что хотя процент работы, выполняемой людьми, будет уменьшаться, общее количество рабочих мест уменьшаться не будет, поскольку будет возрастать количество запросов на использование систем человек+ИИ.

Как долго машинам ещё нужны будут люди?

Но как долго ещё людям будет необходимо тренировать умные системы? ИИ уже справляется с многими заданиями, которые ранее выполнялись людьми.

В 2006, через год после запуска АМТ, директор Amazon Джефф Безос сказал, что раньше, чтобы понять, есть ли определённое лицо на фотографии, нужен был человек, а теперь эту задачу может решить система глубокого обучения, нейросети, работающие в таких компаниях, как Baidu, Facebook, Google и Microsoft. Значит ли это, что микрозадачи, обеспечивающие людям занятость сегодня, завтра отойдут в область машин?

Ледонвирта не считает, что спрос на микрозадачи, связанные с ИИ, будет удовлетворён. Он предсказывает, что чем больше задач можно будет решать при помощи машинного обучения, тем больше данных необходимо будет обработать людям. «Это движущаяся цель. Вариантов задач так много, что я не думаю, что подобная работа закончится в ближайшее время», говорит он.

Бишоп считает, что в ближайшем будущем ИИ будут тренировать гибридно – как через контролируемое обучение под руководством людей, так и через неконтролируемое. Грэй считает, что участие людей будет необходимо ещё очень долго: «Более того, потребность в людях будет возрастать, потому что будет возрастать количество задач, подлежащих автоматизации,- говорит она. – Если взять за образец ранние примеры обработки естественного языка или распознавания образов, станет видно, что работы в системе более чем достаточно».

Доктор Сарвапали Рамчурн [Sarvapali Ramchurn], адъюнкт-профессор в департаменте электроники и информатики Саутгемптонского университета, использует пример распознавания изображений для иллюстрации объёма работ, которые ещё нужно выполнить людям. «Мы и близко не подошли к ограничениям. Разметка изображений всё ещё требует человеческого участия в любой области, в которой были собраны эти изображения».

Фотографии можно получать в таком огромном количестве типов окружения – на свету, в тени, частично заслонённые,- что «даже после классификации 50 миллионов изображений, лишь малая часть запечатлённых там предметов будет точно классифицирована во всех возможных контекстах», говорит он. И добавляет, что если расширить объём работ до распознавания речи, понимания естественного языка, распознавания эмоций и множества других областей, в которых применяют ИИ, будет видно, что поток работы не собирается иссякать. Кроме того, общество постоянно обнаруживает новые области применения ИИ. «Запросы, скорее всего, будут только расти, и мы увидим больше систем, комбинирующих работу людей и ИИ новыми способами с целью решения реальных задач».

Работа как сервис

Нужны ли будут люди для тренировки ИИ в будущем, или нет, рост популярности платформ типа АМТ отражает происходящий сдвиг в области рынка труда.

Грэй считает, что точно так же, как ускорение глобальных коммуникаций позволило отдавать на аутсорс всё больше бизнес-задач, так и краудсорсинговые платформы вкупе с обилием людей, обладающих широкополосным выходом в интернет, изменят рынок труда. «Мы смогли разбить работу полного дня на части с тем, чтобы её в круглосуточном режиме выполняли разные люди в разных часовых поясах из разных мест», говорит она. «Мы не то, чтобы упростили труд или понизили требуемую для его выполнения квалификацию, а, скорее, разбили его на модули, за которые могут браться разные группы людей». Грэй считает, что в будущем такой способ работы, переключение между микрозадачами, станет общепринятым.

Поскольку онлайн-платформы лучше научились быстро связывать заказчиков с исполнителями, имеющими нужный для выполнения задачи опыт, то и использование практики микроработы будет распространяться и расти, говорит она. «Мы наблюдаем индустрию работы, которая назначается, планируется, управляется, оплачивается и отправляет готовые результаты через API», говорит Грэй. «Всё это развивается со скоростью взрыва прямо у нас под носом».

Ледонвирта разделяет видение Грэй в том плане, что компьютерные системы всё чаще будут управлять распределением труда. «Такие вещи, как организация работы при помощи компьютера, использование специальных платформ для регулирования рабочих взаимоотношений, набирают популярность», говорит он.

С ростом количества подключённых к интернету людей и популярности краудсорсинговых платформ правительства должны начать обращать внимание на то, как это влияет на жизнь людей, говорит Грэй. «Нам ещё предстоит понять, как именно такой подход поменяет подход большинства людей к работе», говорит она. «Этот процесс идёт уже 30 лет. Мы не обращали на него внимания, поскольку он не затрагивал людей во власти и их детей».
ссылка на оригинал статьи https://geektimes.ru/post/284244/

Хранение php-сессий в Redis с блокировками

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

Наиболее популярное решение — расширение phpredis. Достаточно установить расширение и настроить php.ini и сессии будут автоматически сохраняться в Redis без изменения кода приложений.

Однако такое решение имеет недостаток — отсутствие блокировки сессии.

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

Это легко проверить. Отправляем на сервер асинхронно 100 запросов, каждый из которых пишет в сессию свой параметр, затем считаем количество параметров в сессии.

Тестовый скрипт

<?php  session_start();  $cmd = $_GET['cmd'] ?? ($_POST['cmd'] ?? '');  switch ($cmd) {     case 'result':         echo(count($_SESSION));         break;     case "set":         $_SESSION['param_' . $_POST['name']] = 1;         break;     default:         $_SESSION = [];         echo '<script src="https://code.jquery.com/jquery-1.11.3.js"></script> <script> $(document).ready(function() {     for(var i = 0; i < 100; i++) {     $.ajax({         type: "post",         url: "?",         dataType: "json",         data: {             name: i,             cmd: "set"         }     });     }      res = function() {         window.location = "?cmd=result";     }      setTimeout(res, 10000); }); </script> ';         break; }

В результате получаем, что в сессии не 100 параметров, а 60-80. Остальные данные мы потеряли.
В реальных приложениях конечно 100 одновременных запросов не будет, однако практика показывает, что даже при двух асинхронных одновременных запросах данные, записываемые одним из запросов, довольно часто затираются другим. Таким образом, использование расширения phpredis для хранения сессий небезопасно и может привести к потере данных.

Как один из вариантов решения проблемы — свой SessionHandler, поддерживающий блокировки.

Реализация

Чтобы установить блокировку сессии, установим значение ключа блокировки в случайно сгенерированное (на основе uniqid) значение. Значение должно быть уникальным, чтобы любой параллельный запрос не мог получить доступ.

    protected function lockSession($sessionId)     {         $attempts = (1000000 * $this->lockMaxWait) / $this->spinLockWait;         $this->token = uniqid();         $this->lockKey = $sessionId . '.lock';         for ($i = 0; $i < $attempts; ++$i) {             $success = $this->redis->set(                 $this->getRedisKey($this->lockKey),                 $this->token,                 [                     'NX',                 ]             );             if ($success) {                 $this->locked = true;                 return true;             }             usleep($this->spinLockWait);         }         return false;     }

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

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

При разблокировке сессии при завершении работы скрипта для удаления ключа используем Lua-сценарий:

    private function unlockSession()     {         $script = <<<LUA if redis.call("GET", KEYS[1]) == ARGV[1] then     return redis.call("DEL", KEYS[1]) else     return 0 end LUA;         $this->redis->eval($script, array($this->getRedisKey($this->lockKey), $this->token), 1);         $this->locked = false;         $this->token = null;     }

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

Полный код класса

class RedisSessionHandler implements \SessionHandlerInterface {     protected $redis;      protected $ttl;      protected $prefix;      protected $locked;      private $lockKey;      private $token;      private $spinLockWait;      private $lockMaxWait;      public function __construct(\Redis $redis, $prefix = 'PHPREDIS_SESSION:', $spinLockWait = 200000)     {         $this->redis = $redis;         $this->ttl = ini_get('gc_maxlifetime');         $iniMaxExecutionTime = ini_get('max_execution_time');         $this->lockMaxWait = $iniMaxExecutionTime ? $iniMaxExecutionTime * 0.7 : 20;         $this->prefix = $prefix;         $this->locked = false;         $this->lockKey = null;         $this->spinLockWait = $spinLockWait;     }      public function open($savePath, $sessionName)     {         return true;     }      protected function lockSession($sessionId)     {         $attempts = (1000000 * $this->lockMaxWait) / $this->spinLockWait;         $this->token = uniqid();         $this->lockKey = $sessionId . '.lock';         for ($i = 0; $i < $attempts; ++$i) {             $success = $this->redis->set(                 $this->getRedisKey($this->lockKey),                 $this->token,                 [                     'NX',                 ]             );             if ($success) {                 $this->locked = true;                 return true;             }             usleep($this->spinLockWait);         }         return false;     }      private function unlockSession()     {         $script = <<<LUA if redis.call("GET", KEYS[1]) == ARGV[1] then     return redis.call("DEL", KEYS[1]) else     return 0 end LUA;         $this->redis->eval($script, array($this->getRedisKey($this->lockKey), $this->token), 1);         $this->locked = false;         $this->token = null;     }      public function close()     {         if ($this->locked) {             $this->unlockSession();         }         return true;     }      public function read($sessionId)     {         if (!$this->locked) {             if (!$this->lockSession($sessionId)) {                 return false;             }         }         return $this->redis->get($this->getRedisKey($sessionId)) ?: '';     }      public function write($sessionId, $data)     {         if ($this->ttl > 0) {             $this->redis->setex($this->getRedisKey($sessionId), $this->ttl, $data);         } else {             $this->redis->set($this->getRedisKey($sessionId), $data);         }         return true;     }      public function destroy($sessionId)     {         $this->redis->del($this->getRedisKey($sessionId));         $this->close();         return true;     }      public function gc($lifetime)     {         return true;     }      public function setTtl($ttl)     {         $this->ttl = $ttl;     }      public function getLockMaxWait()     {         return $this->lockMaxWait;     }      public function setLockMaxWait($lockMaxWait)     {         $this->lockMaxWait = $lockMaxWait;     }      protected function getRedisKey($key)     {         if (empty($this->prefix)) {             return $key;         }         return $this->prefix . $key;     }          public function __destruct()     {         $this->close();     } }

Подключение

$redis = new Redis(); if ($redis->connect('11.111.111.11', 6379) && $redis->select(0)) {     $handler = new \suffi\RedisSessionHandler\RedisSessionHandler($redis);     session_set_save_handler($handler); }  session_start();

Результат

После подключения нашего SessionHandler наш тестовый скрипт уверенно показывает 100 параметров в сессии. При этом несмотря на блокировки общее время обработки 100 запросов выросло незначительно. В реальной практике такого количества одновременных запросов не будет. Однако время работы скрипта обычно более существенно, и при одновременных запросах может быть заметное ожидание. Поэтому нужно думать о сокращении времени работы с сессией скрипта (вызове session_start() только при необходимости работы с сессией и session_write_close() при завершении работы с ней)

Ссылки

» Ссылка на репозиторий на гитхабе
» Страница о блокировках Redis
ссылка на оригинал статьи https://habrahabr.ru/post/318836/

Новогодний колл-центр 2017 с рапознаванием речи и призом

Пятница, вечер, 30 декабря, мы традиционно запилили развлекательный сервис «Новогодний колл-центр 2017». В этом году значительно расширили возможности по распознаванию речи у платформф и поэтому для демонстрации этих возможностей широкой публике сделали мини-игру для хабраюзеров. Каждый желающий может подключиться к конференции и попробовать свои силы в угадывании слов. Правила игры и описание приза под катом.

В игре одновременно участвуют 5 игроков и наш ведущий, остальные желающие ожидают своей очереди и смотрят прямую трансляцию. Время от время ведущему выдается новое слово и включается таймер, после чего ведущий пытается показать или описать это слово, не называя его. Задача игрока — правильно угадать слово. Система распознавания речи в реальном времени анализирует звук с микрофона игрока и первый угадавший слово получает +1 балл. Игроки, которые не смогли угадать слово два раза подряд автоматически отключаются системой от игры, а вместо них подключаются ожидающие своей очереди. Среди игроков, набравших наибольшее количество звонков, мы разыграем такие призы

P.S. На всякий случай ссылка на игру еще раз
ссылка на оригинал статьи https://habrahabr.ru/post/318830/

Мое знакомство с ReactiveCocoa

Честно говоря, я начала использовать ReactiveCocoa, потому что это модно. Я слышу как iOS разработчики говорят об этом фреймворке все время, и я едва могу вспомнить iOS Meetup без упоминание ReactiveCocoa.

image

Когда я только начала изучать ReactiveCocoa я не знала что это такое. «Реактивный» звучит действительно здорово, и «функциональный» звучит умно. Но после того как я поддалась искушению овладеть Reactive Cocoa я уже не могу себе представить написания кода без его использования.

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

Я овладела ReactiveCocoa на реальном проекте, делая некоторые простые вещи, сначала я использовала его для решения двух проблем, о которых я расскажу вам, в этой статье. Я буду говорить «что делать», а не «как сделать», чтобы вы могли получить практическое понимание фреймворка.

1. Связи

Знакосмтво с ReactiveCocoa обычно начинается со связей. В конце концов, они являются самой простой вещью, которую можно понять новичку.

Связи сами по себе являются лишь дополнением к существующему механизму KVO в Objective-C. Есть что-нибудь новое, что ReactiveCocoa приносит в KVO? Это более удобный интерфейс, так же добавляет способность, описать правила связывания состояния модели и состояние на UI в декларативном стиле.

Давайте посмотрим на связи на примере ячейки таблицы.

Обычно ячейка привязывается к модели и отображает ее визуальное состояние (или состояние ViewModel для адептов MVVM). Хотя, ReactiveCocoa часто рассматриваются в едином контексте с MVVM и наоборот, это на самом деле не имеет значения. Связи — это просто способ сделать вашу жизнь проще.

- (void)awakeFromNib {    [super awakeFromNib];    RAC(self, titleLabel.text) = RACObserve(self, model.title); } 

Это декларативный стиль. «Я хочу, чтобы текст моей метке всегда равняться значению Title моей модели» — в методе -awakeFromNib. На самом деле не имеет значения, когда title или модель изменяется.

Когда мы посмотрим на то, как это работает внутри, мы обнаружим, что RACObserve является макросом, который принимает путь ("mode.title" из объекта self в нашем случае) и преобразует его в RACSignal. RACSignal является объектом фреймворка ReactiveCocoa, который представляет и обеспечивает будущие данные. В нашем примере, он будет доставлять данные из "model.title" каждый раз, когда title или модель изменяется.

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

Довольно часто вы должны будете трансформировать состояние модели для отображения ее состояния на UI. В этом случае вы можете использовать оператор -map:

RAC(self, titleLable.text) = [RACObserve(self, model.title) map:^id(NSString *text) {    return [NSString stringWithFormat:@”title: %@”, text]; }] 

Все операции с UI должны быть выполнены в основном потоке. Но, например, поле title может быть изменено в фоновом потоке (т.е. при обработке данных). Вот то, что вам нужно добавить для того, чтобы новое значение title было доставлено абоненту на главном потоке:

RAC(self, titleLabel.text) = [RACObserve(self, model.title) deliverOnMainThread]; 

RACObserve это расширеный макрос -rac_valuesForKeyPath:observer: Но вот уловка — этот макрос всегда захватывает self в качестве наблюдателя. Если вы используете RACObserve внутри блока, вы должны убедиться, что вы не создаете цикличность ссылок и используете слабую ссылку. ReactiveCocoa имеет удобные макросы @weakify и @strongify для этих нужд.

Еще одна деталь о которой нужно предупредить о связях — это случай, когда ваше состояние модели привязано к некоторым существенным изменениям пользовательского интерфейса, а также к частым изменениям состояния модели. Это может негативно повлиять на производительность приложения и, чтобы избежать этого вы можете использовать оператор -throttle: — он принимает NSTimeInterval и посылает команду «next» абоненту после заданного интервала времени.

2. Операции над коллекциями (filter, map, reduce)

Операции над коллекциям были следущим, что мы исследовали в ReactiveCocoa. Работа с массивами занимает много времени, не так ли? В то время как ваше приложение работает, массивы данных приходят к вам с сети и требуют модификации, так чтобы вы могли представить их пользователю в необходимой форме.

Необработанные данные из сети должны быть преобразованы в обьект или View Models, и отображены пользователю.

В ReactiveCocoa, коллекции представлены как класс RACSequence. Есть категории для всех типов Cocoa коллекций, которые преобразуют Cocoa коллекции в коллекции ReactiveCocoa. После этих преобразований, вы получите несколько функциональных методов, такие как map, filter и reduce.

Вот небольшой пример:

 RACSequence *sequence = [[[matchesViewModels rac_sequence] filter:^BOOL(MatchViewModel *match) {     return [match hasMessages];  }] map:^id(MatchViewModel *match) {     return match.chatViewModel;  }]; 

Во-первых, мы фильтруем наши view models, чтобы выбрать те, которые уже имеют сообщения ( — (BOOL)hasMessages). После чего мы должны превратить их в другие view models.

После того как вы закончили с последовательностю, она может быть преобразована обратно в NSArray:

NSArray *chatsViewModels = [sequence array]; 

Вы заметили, что мы снова используем оператор -map:? На этот раз, хотя, это и относится к RACSequence, а не RACSignal, как это было со связями.

Самым замечательным в архитектуре RAC является то, что она имеет только два основных класса — RACSignal и RACSequence, которые имеют одного родителя — RACStream. Все поток, а сигнал является толчком приводящи поток в движение (новые значения выталкиваются к подписчикам и не могут быть выведены), а последовательность является выдвижным приводом потока (обеспечивает значения, когда кто-то о них просит).

Еще одна вещь, которую стоит отметить, это как мы связываем операции вместе. Это ключевое понятие в RAC, которое также применяется в RACSignal и RACSequence.

3. Работа с сетью

Следующим шагом в понимании особенностей фреймворка, это использование его для работы в сети. Когда я говорила о связях, я упомянула, что маркос RACObserve создает RACSignal, который представляет данные, которые будут доставлены в будущем. Этот объект идеально подходит для представления сетевого запроса.

Сигналы отправляют три типа событий:

  • next — будущее значение/значения;
  • error — Значение NSError*, что означает, что сигнал не может быть успешно завершен;
  • completed — означает, что сигнал был успешно завершен.

Срок службы сигнала состоит из любого числа next событий, а затем одного error или completed (но не обоих).

Это очень похоже на то, как мы писали наши сетевые запросы, используя блоки. Но в чем разница? Зачем заменять обычные блоки сигналами? Вот некоторые причины:

1) Вы избавляетесь от обратного вызова!

Этот кошмар в коде происходит, когда у вас есть несколько вложенных запросов и каждый последующи использует результат предыдущего.

2) Вы обрабатываете ошибку в одном месте.
Вот небольшой пример:

Предположим, у вас есть два сигнала — loginUser и fetchUserInfo. Давайте создадим сигнал, который «логинет» пользователя, и затем получает его данные:

RACSignal *signal = [[networkClient loginUser] flattenMap:^RACStream *(User *user) {    return [networkClient fetchUserInfo:user]; }]; 

Блок flattenMap будет вызываться, когда сигнал loginUser посылает событие next, и это значение переходит к блоку через параметр user. В блоке flattenMap мы берем это значение из предыдущего сигнала и производим новый сигнал в качестве результата. Теперь, давайте подпишемся на этот сигнал:

[signal subscribeError:^(NSError *error) {  // error from one of the signals   } completed:^{  // side effects goes here. block get called when both signals completed  }]; 

Стоит отметить, что блок subscribeError будет вызываться в том случае если по крайней мере один из сигналов не сработает. Если первый сигнал завершается с ошибкой, второй сигнал не выполниться.

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

[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {  __block NSURLSessionDataTask *task = [self GET:url parameters:parameters completion:^(id response, NSError *error) {    if (!error) {        [subscriber sendNext:response];        [subscriber sendCompleted];    } else {        [subscriber sendError:error];    }  }];   return [RACDisposable disposableWithBlock:^{     [task cancel];  }]; }]]; 

Я намеренно упростила этот код, чтобы показать идею, но в реальной жизни вы, не должны захватывать self в блоке.

Ссылкой на сигнал можно определить, когда он должен быть отменен:

[[networkClient loadChats] takeUntil:self.rac_willDeallocSignal]; 

или

[[networkClient loadChats] takeUntil:[self.cancelButton rac_signalForControlEvents:UIControlEventTouchUpInside]]; 

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

Конечно, вы также можете отменить сигнал вручную — просто хранить ссылку на объект RACDisposable (который возвращается из метода subsribeNext/Error/Completed) и вызвать непосредственно метод -dispose, когда есть такая необходимость.

Реализация сетевого клиента с использованием сигналов является довольно обширной темой для обсуждения. Вы можете посмотреть на OctoKit — отличный пример того, как использоваться Reactive Cocoa для решения сетевых вопросов. Ash Furrow, также покрыл эту тему в своей книге Функциональное реактивное программирование под iOS.

4. Сигналы в действии

При решении некоторых задач, мы объединяли части данных и событий из разных частей приложения. Данные появляются и изменяются асинхронно. Если мы думаем о них этом императивно, мы стараемся предвидеть, какие дополнительные соединения и переменные должны прийти в коде и, что более важно, как синхронизировать все это вовремя.

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

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

Через некоторое время работы с Reactive Cocoa, ко мне пришло понимание, что основое решения всех упомянутых выше задач (связывание, операции над колекциями, работа с сетью) представляет жизненный цикл приложения в качестве потока данных (RACStream). Затем данные, поступающие от пользователя или с сети должны быть преобразованы определенным способом. Оказывается можно решить поставленные задачи гораздо проще!

Давайте рассмотрим два примера.

Задача #1
Это пример из реального проекта, который мы недавно закончили.

У нас была возможность обмена сообщениями и одной из задач было отобразить правильное количество непрочитах сообщений на иконке приложения. Обычная задача, не так ли?

У нас был класс ChatViewModel с которых хранил логическое свойство unread.

@interface ChatViewModel : NSObject  @property(nonatomic, readonly) BOOL unread  // other public declarations  @end 

И где-то в коде, мы имели массив dataSourc, содержащий эти view models.

Что мы хотим сделать? Мы хотим обновлять количество непрочитаных сообщений каждый раз когда меняется unread свойство. Количество елементов должно равняться количеству значений YES, во всех моделях. Давайте сделаем такую выобрку с помощью сигнала:

RACSignal *unreadStatusChanged = [[RACObserve(self, dataSource) map:^id(NSArray *models) {    RACSequence *sequence = [[models rac_sequence] map:^id(ChatViewModel *model) {       return RACObserve(model, unread);  }];     return [[RACSignal combineLatest:sequence] map:^id(RACTuple *unreadStatuses) {    return [unreadStatuses.rac_sequence foldLeftWithStart:@0 reduce:^id(NSNumber *accumulator,    NSNumber *unreadStatus) {    return @(accumulator.integerValue + unreadStatus.integerValue);  }];  }]; }] switchToLatest]; 

Это может выглядеть немного сложно для новичков, но это довольно легко понять.

Во-первых, мы наблюдаем за изменениями в массиве:

RACObserve(self, dataSource) 

Это важно, потому что предполагается, что могут быть созданные новые чаты, и старые могут быть удалены. Так RAC нету KVO для изменяемых колекций, DataSource является неизменяемы массивом каждый раз, когда объект добавлен/удален из/в dataSource. RACObserv вернет сигнал, который будет возращать новый массив каждый раз, когда в dataSource будет добавленно новое значение.

Хорошо, мы получили сигнал… Но это не тот сигнал, который мы хотели, таким образом, мы должны преобразовать его. Оператор -map: прекрасно подойдет для этой задачи.

[RACObserve(self, dataSource) map:^id(NSArray *models) { }] 

Мы получили множество моделей в блоке map. Так как мы хотим знать о каждом изменении свойства unread всех моделий, кажется, что мы все еще нуждаемся в сигнале, или даже массиве сигналов — один сигнал для каждой модели:

 RACSequence *sequence = [[models rac_sequence] map:^id(ChatViewModel *model) {     return RACObserve(model, unread);  }]; 

Ничего нового здесь нет. RACSequence, map, RACObserve.

Примечание: В этом случае, мы преобразовуем нашу последовательность значений в последовательность сигналов.

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

+merge, будет пересылать значения от наших сигналов в единый поток. Это точно не соответствует нашим потребностям, в следующем блоке мы увидим только последнее значение (в нашем случае YES или NO).

Так как мы хотим знать все значения (для того, чтобы получить их сумму), давайте использовать + combineLatest: Он будет следить за изменением сигналов, а затем, отправлять последнее значения всех сигналов, когда происходит изменение. В следующем блоке мы можем видеть «снимок» всех наших непрочитанных значений.

[RACSignal combineLatest:sequence]; 

Теперь мы можем получать массив последних значений каждый раз, когда изменяется одиночные значения. Почти закончено! Единственной задачей осталось подсчитать, сколько раз встречается значение YES в этом массиве. Мы можем сделать это с помощью простого цикла, но давайте быть функциональными до конца и использовать оператор reduce. reduce является известной функцией в функциональном программировании, которая преобразует сбор данных в единую атомную величину заранее определенным правилом. В RAC эта функция -foldLeftWithStart:reduce: или -foldLeftWithStart:reduce:.

[unreadStatuses.rac_sequence foldLeftWithStart:@0 reduce:^id(NSNumber *accumulator, NSNumber *unreadStatus) {    return @(accumulator.integerValue + unreadStatus.integerValue); }]; 

Последнее, что остается неясным, зачем нам нужен switchToLatest?

Без него мы получим сигнал сигналов (так как мы преобразовуем значение массива в сигнал), и если вы подпишитесь на unreadStatusChanged, вы получите сигнал в следующем блоке, а не значения. Мы можем использовать либо -flatten или -switchToLatest (которая flattened, но с небольшой разницей), чтобы исправить это.

flatten означает, что подписчик, который в настоящее время flattened получит значения, отправленное с помощью сигнала, который возвращается из преобразования. В то время как -flatten принимает сигнал сигналов и объединяет их вместе следующие значения, отправленные любому из них, -switchToLatest делает то же самое, но перенаправляет значения только из последнего сигнала.

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

RAC([UIApplication sharedApplication], applicationIconBadgeNumber) = unreadStatusChanged; 

Вы заметили, как постановка задачи связана с кодом? Мы только что написали, что декларативно мы хотим сделать в одном месте. Нам не нужно сохранить промежуточное состояние.

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

Задача #2
Вот еще одна задача, которая демонстрирует возможности фреймворка. У нас был экран списка чатов, и задача была: при открытые экрана с списком чатов, отображать последниее сообщение чата. Вот как выглядит созданный сигнал:

RACSignal *chatReceivedFirstMessage = [[RACObserve(self, dataSource) map:^id(NSArray *chats) {    RACSequence *sequence = [[[chats rac_sequence] filter:^BOOL(ChatViewModel *chat) {       return ![chat hasMessages];    }] map:^id(ChatViewModel *chat) {       return [[RACObserve(chat, lastMessage) ignore:nil] take:1];    }] ;     return [RACSignal merge:sequence]; }] switchToLatest]; 

Давайте посмотрим, из чего он состоит.

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

Сначала мы фильтруем наш dataSource в блоке преобразования, потому что мы не заинтересованы в чатах, которые имеют сообщения.

Тогда мы преобразовываем значения в сигналы, опять же с помощью RACObserve.

return [[RACObserve(chat, lastMessage) ignore:nil] take:1]; 

Поскольку сигнал, создаваемый RACObserve будет запускатся с начальным значением свойства, которое будет равно nil, мы должны игнорировать его -ignore:. Оператор это то, что нам нужно.

Вторая часть задачи для того чтобы учитывать только первое входящее сообщение -Take:. Будет заботиться об этом. Сигнал будет завершен (и удален) сразу после получения первого значения.

Просто, для того чтобы все прояснить. Есть три новых сигнала, которые мы создали в этом коде. Первый был создан макросом RACObserve, второй по вызову -ignore: оператора на первом вновь созданном сигнале, а третий, по вызову -take: по сигналу, созданному -ignore:

Как и в предыдущем примере, нам нужен один сигнал на основе созданных сигналов. Мы используем -merge: для создания нового объединенного потока, так как мы не заботимся о значениях, как в предыдущем примере.

Время побочного эффекта!

[chatReceivedFirstMessage subscribeNext:^(id x) {  // switching to chat screen  }]; 

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

Теперь давайте немного поговорим о впечатлениях от Reactive Cocoa.

Что мне действительно нравится в Reactive Cocoa

1. Его легко начать использовать в проектах. Фреймворк задокументален, как сумасшедший. На GitHub есть много примеров, с подробным описанием каждого класса и метода, большое количество статей, видео и презентаций.

2. Вам не нужно полностью изменять свой стиль программирования. Во-первых, вы можете использовать существующие решения для проблем, таких как связывания UI, сетевые обертки, и другие решения с GitHub. Затем, шаг за шагом, вы можете понять все возможности Reactive Cocoa.

3. Он действительно меняет способ решения задач от императивниго к декларативному. Теперь, когда функциональный стиль программирования становится все более и более популярным в IOS сообществе, трудно для многих в корне изменить свой образ мышления. Reactive Cocoa помогает внести изменения, потому что он имеет много инструментов, которые помогут вам общаться в стиле «что делать», а не "как сделать".

То, что я не нравиться в Reactive Cocoa

1. Широкое использование макросов RAC () или RACObserve ().

2. Иногда может быть трудно отлаживать код, так как использование RACSignals приводит к глубоким трассировкам стека.

3. Не type-safe (вы никогда не знаете, какой тип ожидать в блоке subscribeNext). Лучшее решение в этом случае, является документирование сигналов в публичном интерфейсе, как пример:

/** * Returns a signal which will send a User and complete or error. */ -(RACSignal *)loginUser; 

Я также не могу не упомянуть Swift
Reactive Cocoa написан на Objective-C и специально для Objective-C. Но, конечно, сейчас, когда Swift набирает популярность, разработчики фреймворков не сидят без дела. Они на самом деле пишут Swift API, для использования с Reactive Cocoa (Великий Swiftening близко). Скромненькая мы увидеть новую версию 3.0 с блэкджеком и шлюхами, дженериками и перегрузкой операторов.
Я уверена, что после этого RAC получит еще больше поклонников. Вскоре перфекционисты, которые проклинают макросы и не-безопасность типов не будут иметь аргументов, чтобы защитить себя и не использовать Reactive Cocoa.

Вывод

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

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

Кроме того, вы должны помнить, что все события доставляются абонентам в потоке, в котором они были созданы. Если вам нужно указать, что, применять данную RACScheduler (класс, похожий на очереди GCD, но с возможностью отмены) с помощью оператора -deliverOn:

Как правило, вы должны явно указать только [RACScheduler mainThreadScheduler] для обновления интерфейса, но вы можете написать свою собственную реализацию RACSceduler, когда вы имеете дело с чем-то конкретным, как CoreData.
ссылка на оригинал статьи https://habrahabr.ru/post/317992/

Теории заговора. Что скрыло НАСА в 2016 году


Один из уфологов заметил куб боргов, который как будто восстанавливает энергетический запас от энергии Солнца

Обзор достижений космонавтики в 2016 году будет неполным без теорий заговора, которые всегда сопровождают космические программы НАСА. Люди в шапочках из фольги до сих пор увлечённо спорят, в какой именно пустыне НАСА снимало фильм о высадке на Луну и на какой конкретно секретной базе хранят останки инопланетян. Некоторые уверены, что рептилоиды уже проникли в правительство, а сам Илон Маск родился на марсианской колонии — и сейчас жаждет вернуться на Родину.

В 2016 году уфологам удалось разоблачить несколько новых тайн и коварных секретов НАСА. Это делать легко, потому что НАСА давно выкладывает в интернет все научные данные и фотографии, сделанные космическими аппаратами.

Издание Ars Technica ежегодно развлекается, запуская поиск в интернете по фразе [“NASA hiding”]. Сейчас поиск возвращает 57 100 документов.

О существовании жизни в космосе уфологам известно давно. Эти слухи подтвердились ещё в 2014 году, когда российские учёные обнаружили следы морского планктона на иллюминаторах снаружи Международной космической станции. Об этом уникальном открытии сообщил руководитель полётом российского сегмента МКС Владимир Соловьёв.

Поостейшие микроорганизмы — конечно же, не единственные существа в околоземном пространстве. На одной из видеосъёмок с борта МКС в прошлом году многие заметили загадочный розовый объект, висящий рядом со станцией.

Учёные попытались выдать этот объект за какой-то блик — отражение чего-то внутри станции.

Но это не единственная фотография с МКС с загадочными объектами в кадре. Например, длинный сигарообразный объект попал в кадр во время съёмки Южной Индии с борта МКС в ноябре 2015 года. Его хорошо видно в правом верхнем углу фотографии.

Информация мгновенно разошлась по соцсетям, получила тысячи ретвитов и лайков. Видеоролик с НЛО набрал почти миллион просмотров. Учёные опять нашли постороннее объяснение феномену. Они сказали, что там якобы часть конструкции МКС.

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

Конспирологи абсолютно уверены, что НАСА редактирует видеопоток с борта МКС. Эти слухи особенно усилились в июле 2016 года, когда НАСА внезапно отключило прямую видеотрансляцию с борта станции как раз в тот момент, когда в поле зрения камеры попал неопознанный летающий объект.

Объект начал медленно опускаться на Землю, когда трансляция внезапно прервалась из-за технических проблем. Представитель НАСА в официальном комментарии заявил, что трансляция не была прервана специально, а камеры на борту МКС работают в автоматическом режиме. Просто станция периодически выходит из зоны приёма спутников системы сопровождения объектов и передачи данных (TDRSS), которые принимают видеопоток с камер.

Один из бдительных пользователей под ником Streetcap1 записал видеопоток прямо перед срывом трансляции — и выложил его на YouTube. За прошедшие месяцы видео посмотрели около 4 млн раз. Большинство пользователей, вероятно, верят в теорию заговора, потому что количество лайков в семь раз превышает количество дислайков.

Неизвестно, как связана посадка странного объекта на Землю с массивной дырой прямо на Северном полюсе нашей планеты, которая якобы открылась за пару месяцев до этого. В мае 2016 года о портале проинформировал человечество обеспокоенный пользователь secureteam10.

К настоящему времени 13-минутное видео набрало 2,3 млн просмотров. Автор профессионально нагнетает обстановку, компилируя множество старых спутниковых фотографий, которые доказывают, что НАСА «поймано с поличным в попытках скрыть правду».

В фильме присутствует фрагмент видеозаписи НАСА во время полёта над Антарктидой, когда прямо во время подлёта к порталу астронавт внезапно закрывает объектив камеры. Очень загадочно.

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

Да и научную станцию на Южном полюсе ещё никуда не засосало.

Кстати, упомянутый автор secureteam10 давно специализируется на разоблачениях заговоров НАСА. В другом видео он доказывает, что НАСА скрывает некий объект на фотографиях Солнца, сделанных космическим аппаратом Solar and Heliospheric Observatory (SOHO).

Этот кубический объект, который «высасывает энергию из Солнца», агентство НАСА выдаёт за «пиксельные глюки».

По мнению уфологов, объяснение правительства не выдерживает никакой критике, потому что кубический объект сфотографирован двумя разными камерами в разное время, а куб остался в прежнем месте, «продолжая высасывать энергию из нашей звезды».

Если вы тоже заметили странные объекты на фотографиях Марса, Солнца и с околоземной орбиты, сообщайте в комментариях.
ссылка на оригинал статьи https://geektimes.ru/post/284242/