О смарт-контрактах простыми словами |
Метки: author Ramon алгоритмы блог компании «лаборатория касперского» ethereum blockchain |
Пару слов о неминуемом повороте в развитии IT-отрасли |
Метки: author botyaslonim управление сообществом управление разработкой карьера в it-индустрии венчурные инвестиции карьера программиста отрасль it- компании |
Что общего у крупных успешных Open Source-проектов? |
Метки: author shurup управление сообществом управление проектами блог компании флант open source linux foundation сообщество статистика |
Немного из истории криптографии СССP: M-105 под кодовым названием Агат |
Код Бодо — равномерный телеграфный 5-битный код, использующий два отличающихся друг от друга электрических сигнала.
|
Народная Политика конфиденциальности |
|
Вторая встреча амазоновцев в Москве |
Метки: author tonyzorin учебный процесс в it aws amazon web services обучение |
RPM-репозиторий — своими руками |
Итак, начнём.
При внедрении DevOps-процесса в компании одним из возможных вариантов хранилища артефактов сборки может стать rpm-репозиторий. По существу — это просто веб-сервер, раздающий определённым образом организованное содержимое. Есть, конечно, коммерческие варианты maven-репозиториев, которые имеют плагины для поддержки rpm, но мы же не ищем лёгких путей?
Написать сервис, который будет принимать готовые rpm-пакеты по протоколу HTTP, парсить их метаданные, раскладывать файлы пакетов по каталогам в соответствии с внутренней структурой репозитория и обновлять метаданные репозитория после обработки очередного пакета. Что из этого получилось — описано под катом.
В моей голове задача почти мгновенно распалась на несколько частей: первая — принимающая, которая должна принять rpm-пакет по HTTP; вторая — обрабатывающая, которая должна принятый RPM-пакет обработать. Ну и где-то ещё должен быть веб-сервер, который будет раздавать содержимое репозитория.
Ввиду того, что с Nginx я знаком давно, выбор веб-сервера для приёма rpm-пакетов и раздачи содержимого репозитория даже не стоял — только Nginx. Приняв это как данность, я нашёл в документации нужные опции и написал
location /upload {
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:5000;
proxy_pass_request_body off;
proxy_set_header X-Package-Name $request_body_file;
client_body_in_file_only on;
client_body_temp_path /tmp/rpms;
client_max_body_size 128m;
}
Результат данной конфигурации — при приёме файла Nginx сохранит его в заданный каталог и сообщит исходное имя в отдельном заголовке.
Для полноты картины — вторая крохотная
location /repo {
alias /srv/repo/storage/;
autoindex on;
}
Итак, у нас есть первая часть, которая умеет принимать файлы и отдавать их.
Обрабатывающая часть напиcана на Python без особых премудростей и выглядит
#!/usr/bin/env python
import argparse
import collections
import pprint
import shutil
import subprocess
import threading
import os
import re
import yaml
from flask import Flask, request
from pyrpm import rpmdefs
from pyrpm.rpm import RPM
# Сервис для поддержания репозиториев (С) Sergey Pechenko, 2017
# Лицензия - GPL v2.0. Никаких дополнительных гарантий или прав не предоставляется.
# Для лицензирования использования кода в коммерческом продукте свяжитесь с автором.
class LoggingMiddleware(object):
# Вспомогательный класс для логирования запросов и отладки
def __init__(self, app):
self._app = app
def __call__(self, environ, resp):
errorlog = environ['wsgi.errors']
pprint.pprint(('REQUEST', environ), stream=errorlog)
def log_response(status, headers, *args):
pprint.pprint(('RESPONSE', status, headers), stream=errorlog)
return resp(status, headers, *args)
return self._app(environ, log_response)
def parse_package_info(rpm):
# Обработка метаданных пакета
os_name_rel = rpm[rpmdefs.RPMTAG_RELEASE]
os_data = re.search('^(\d+)\.(\w+)(\d+)$', os_name_rel)
package = {
'filename': "%s-%s-%s.%s.rpm" % (rpm[rpmdefs.RPMTAG_NAME],
rpm[rpmdefs.RPMTAG_VERSION],
rpm[rpmdefs.RPMTAG_RELEASE],
rpm[rpmdefs.RPMTAG_ARCH]),
'os_abbr': os_data.group(2),
'os_release': os_data.group(3),
'os_arch': rpm[rpmdefs.RPMTAG_ARCH]
}
return package
# Объект приложения и его настройки
app = Flask(__name__)
settings = {}
# Тестовый обработчик - пригодится в начале настройки
@app.route('/')
def hello_world():
return 'Hello from repo!'
# Обработчик конкретного маршрута в URL
@app.route('/upload', methods=['PUT'])
def upload():
# Ответ по умолчанию
status = 503
headers = []
# Этот нестандартный заголовок мы добавили в конфигурацию Nginx ранее
curr_package = request.headers.get('X-Package-Name')
rpm = RPM(file(unicode(curr_package)))
rpm_data = parse_package_info(rpm)
try:
new_req_queue_element = '%s/%s' % (rpm_data['os_release'], rpm_data['os_arch'])
dest_dirname = '%s/%s/Packages' % (
app.settings['repo']['top_dir'],
new_req_queue_element)
# Перемещаем файл в нужный каталог
shutil.move(curr_package, dest_dirname)
src_filename = '%s/%s' % (dest_dirname, os.path.basename(curr_package))
dest_filename = '%s/%s' % (dest_dirname, rpm_data['filename'])
# Переименовываем файл
shutil.move(src_filename, dest_filename)
# Готовим ответ, который получит загружавший клиент
response = 'OK - Accessible as %s' % dest_filename
status = 200
if new_req_queue_element not in req_queue:
# Кладём запрос на обработку этого пакета в очередь
req_queue.append(new_req_queue_element)
event_timeout.set()
event_request.set()
except BaseException as E:
response = E.message
return response, status, headers
def update_func(evt_upd, evt_exit):
# Ждёт события, затем запускает обновление метаданных
while not evt_exit.is_set():
if evt_upd.wait():
# Выбираем следующий доступный запрос из очереди
curr_elem = req_queue.popleft()
p = subprocess.Popen([app.settings['index_updater']['executable'],
app.settings['index_updater']['cmdline'],
'%s/%s' % (app.settings['repo']['top_dir'], curr_elem)],
shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
res_stdout, res_stderr = p.communicate(None)
pprint.pprint(res_stdout)
pprint.pprint(res_stderr)
# Сбрасываем событие обновления
evt_upd.clear()
return
def update_enable_func(evt_req, evt_tmout, evt_upd, evt_exit):
while not evt_exit.is_set():
# Ожидаем запрос
evt_req.wait()
# OK, дождались
# Теперь выдерживаем 30 секунд, а если в это время пришёл запрос....
while evt_tmout.wait(30) and (not evt_exit.is_set()):
evt_tmout.clear()
if evt_exit.is_set():
break
evt_upd.set()
evt_tmout.clear()
evt_req.clear()
return
def parse_command_line():
# Разбор агрументов командной строки
parser = argparse.ArgumentParser(description='This is a repository update helper')
parser.prog_name = 'repo_helper'
parser.add_argument('-c', '--conf', action='store', default='%.yml' % prog_name, type='file', required='false',
help='Name of the config file', dest='configfile')
parser.epilog('This is an example of Nginx configuration:\
location /repo {\
alias /srv/repo/storage/;\
autoindex on;\
}\
\
location /upload {\
client_body_in_file_only on;\
client_body_temp_path /tmp/rpms;\
client_max_body_size 128m;\
proxy_http_version 1.1;\
proxy_pass http://localhost:5000;\
proxy_pass_request_body off;\
proxy_set_header X-Package-Name $request_body_file;\
}\
')
parser.parse_args()
return parser
def load_config(fn):
with open(fn, 'r') as f:
config = yaml.safe_load(f)
return config
def load_hardcoded_defaults():
# Прибитые гвоздями настройки "по умолчанию"
config = {
'index_updater': {
'executable': '/bin/createrepo',
'cmdline': '--update'
},
'repo': {
'top_dir': '/srv/repo/storage'
},
'server': {
'address': '127.0.0.1',
'port': '5000',
'prefix_url': 'upload',
'upload_header': ''
},
'log': {
'name': 'syslog',
'level': 'INFO'
}
}
return config
if __name__ == '__main__':
try:
cli_args = parse_command_line()
settings = load_config(cli_args['configfile'])
except BaseException as E:
settings = load_hardcoded_defaults()
req_queue = collections.deque()
# Application-level specific stuff
# Exit flag
exit_flag = False
# Событие, сигналящее о пришедшем запросе
event_request = threading.Event()
# Событие, сигналящее об окончании задержки
event_timeout = threading.Event()
# Событие, сигналящее о запуске обновления метаданных
event_update = threading.Event()
# Событие, сигналящее о завершении вспомогательных потоков
event_exit = threading.Event()
# Готовим начальное состояние событий
event_request.clear()
event_timeout.clear()
event_update.clear()
# Поток, который запускает обновление метаданных репозитория
update_thread = threading.Thread(name='update_worker', target=update_func, args=(event_update, event_exit))
update_thread.start()
# Поток, отсчитывающий время задержки, и начинающий отсчёт сначала, если задержка прервана
# Если задержка прервана - начинаем отсчёт сначала
delay_thread = threading.Thread(name='delay_worker', target=update_enable_func,
args=(event_request, event_timeout, event_update, event_exit))
delay_thread.start()
# Его Величество Приложение
app.wsgi_app = LoggingMiddleware(app.wsgi_app)
app.run(host=settings['server']['address'], port=settings['server']['port'])
# Это событие заставит оба дополнительных потока завершиться
event_exit.clear()
Важный и, скорее всего, непонятный с первого взгляда момент — зачем же здесь нужны потоки, события и очередь.
Они нужны для передачи данных между асинхронными процессами. Вот смотрите, ведь HTTP-клиент не обязан ждать какого-то разрешения, чтобы загрузить пакет? Правильно, он может начать загрузку в любой момент. Соответственно, в основном потоке приложения мы должны сообщить клиенту об успешности/неуспешности загрузки, и, если загрузка удалась, передать данные через очередь другому потоку, который выполняет вычитывание метаданных пакета, а затем перемещение его в файловой системе. При этом отдельный поток следит, прошло ли 30 секунд с момента загрузки последнего пакета, или нет. Если прошло — метаданные репозитория будут обновлены. Если же время ещё не вышло, а уже пришёл следющий запрос — сбрасываем и перезапускаем таймер. Таким образом, всякая загрузка пакета будет отодвигать обновление метаданных на 30 секунд.
Сначала нужно
appdirs==1.4.3
click==6.7
Flask==0.12.1
itsdangerous==0.24
Jinja2==2.9.6
MarkupSafe==1.0
packaging==16.8
pyparsing==2.2.0
pyrpm==0.3
PyYAML==3.12
six==1.10.0
uWSGI==2.0.15
Werkzeug==0.12.1
К сожалению, я не могу гарантировать, что это минимально возможный список — команда pip freeze просто берёт список доступных пакетов Python и механически переносит его в файл, не рассматривая, используется ли конкретный пакет в конкретном проекте или нет.
Затем нужно установить пакеты с nginx и c createrepo:
yum install -y nginx createrepo
Запуск проекта выглядит вот так:
nohup python app.py
После того, как всё будет запущено, можно пробовать загрузить rpm-пакет в репозиторий вот такой командой:
curl http://hostname.example.com/upload -T
Я понимаю, что описанный сервис далёк от совершенства и являет собой скорее прототип, нежели полноценное приложение, но, с другой стороны, он может быть легко дополнен/расширен.
Для удобства желающих код выложен на GitHub. Предложения по дополнению сервиса, а ещё лучше — pull-request'ы горячо приветствуются!
Надеюсь, этот прототип окажется кому-то полезным. Спасибо за внимание!
Ну и для тех, кому очень нужно, небольшой сниппет для укрощения SELinux:
#!/bin/bash
semanage fcontext -a -t httpd_sys_rw_content_t "/srv/repo/storage(/.*)?"
restorecon -R -v /srv/repo/storage
setsebool -P httpd_can_network_connect 1
Метки: author tnt4brain серверное администрирование devops *nix devops (*nix) nginx python rpm |
Куда податься вендору, когда Amazon не по зубам: придумываем торговую площадку для нишевых гаджетов |
Метки: author HamsterMrkt управление e-commerce венчурные инвестиции бизнес-модели блог компании hamster marketplace hamster marketplace инди-электроника гаджеты diy |
Математика для программиста |
Нужна ли математика программисту?
Нужна. А, кроме неё, нужна сферическая геометрия, география, музыка и банковское дело. И я сейчас не шучу.
Дело в том, что программисты редко решают задачи для самих себя: мы работаем в банковских сервисах, сервисах бронирования отелей, картографических сервисах и прочих Яндекс.Почтах. Получается, что мы решаем задачи наших пользователей.
Для решения чисто программистских задач у нас есть алгоритмы и паттерны: если посмотреть на код интернет-магазина цветов и банковского сайта он будет очень похож. Будут использоваться одинаковые условия, одинаковые циклы и даже паттерн MVC будет один и тот же.
Важнее то, что стоит за этими вещами: понимание как работает система в целом. Если посмотреть на вещи с этой стороны, то станет понятно, что программист — это младший специалист в области, в которой работает сайт.
Ещё пять лет назад Артём Поликарпов доказал, что каждый фронтендер немного дизайнер. Нам нужно понимать как устроены шрифты: что такое гротеск, чем он отличается от антиквы, что такое интерлиньяж, кернинг, разрядка, капитель. Понимать, что такое сетки и что такое композиция. Кроме этого, разбираться в UX — мы должны знать что такое оптимистичный UI, где поставить прелоадер и зачем это всё нужно пользователю.
Но быть только дизайнером — мало. Дело в том, что пользователи взаимодействуют с нашими сайтами: в интернет-магазинах они вводят данные своих банковских карт, на картографических сервисах прокладывают маршруты и измеряют расстояния, на музыкальных сайтах они транспонируют тональность песен и настраивают гитару по тюнеру. И всё это должен кто-то запрограммировать. Получается, что у программиста должны быть специальные знания.
Например, правильность номера банковской карты определяется по алгоритму Луна — это теория кодирования.
Чтобы найти расстояние между двумя точками на карте, заданными широтой и долготой, нужно воспользоваться формулой дуги большого круга — это сферическая геометрия. Ещё этой формулой очень часто пользуются в морской навигации.
С картами, вообще, связано очень много интересного. Например, Яндекс.Карты используют эллиптическую проекцию Меркатора, а все остальные географические сервисы — сферическую, поэтому если вы захотите наложить слой Яндекс.Пробок на любую другую карту у вас не сойдутся улицы и вам нужно будет знать, как трансформировать одну проекцию в другую.
Кстати, о трансформациях. Помните CSS-фильтр matrix
? Те самые числа в странном порядке, которые непонятно как влияют на поведение вашего блока? Это матрицы перехода из линейной алгебры. Если разобраться что такое матрицы и как перемножать их между собой, то вы сможете очень эффективно писать трансформации не пользуясь алиасами.
.neo {
transform: matrix(0, -1, 1, 0, 50px, 50px);
}
С кругозором понятно — изучайте всё, что хотите, потому что в любом случае это вам пригодится. Но есть ли какая-то общая область, которая нужна всем программистам? Да, такая область есть, она называется «дискретная математика». Наука, которая лежит в основе информатики.
Я не говорю, что нужно учить диксретку досконально. Для программиста важнее широта взглядов и понимание, где посмотреть, чем узкие знания в какой-то отдельной области. Но помнить несколько основных тем не помешает.
Во-первых, изучите булеву логику. Вы пишете условия каждый день и хорошо бы понимать, как они работают, например, для того, чтобы эффективно их упрощать.
Ещё одна хорошая тема из дискретки — это графы. Очень многие программистские задачи решаются с помощью графов. Даже скучный и привычный DOM — это дерево, частный случай графа. И здесь неплохо бы понимать хотя бы как по деревьям можно ходить.
Например, вы знаете, что querySelector
использует поиск в глубину? Это значит, что когда он заходит на узел DOM-дерева, он пытается посмотреть сначала его дочерние узлы и только потом соседние. Это значит если вы будете искать с помощью querySelector
первый элемент на странице, то необязательно это будет элемент верхнего уровня, найденный элемент может находиться на любой вложенности.
const firstDiv = document.querySelector('div');
firstDiv.id === 'underdog';
Ещё одна тема из дискретной математики — алгоритмы. Теория алгоритмов изучает что такое алгоритмы и оценку их эффективности. Представьте, у вас есть список людей, у которых вам нужно посчитать средний рост. Список задан в виде массива объектов.
const people = [
{ name: 'Иван', height: 183 },
{ name: 'Марья', height: 155 },
];
Первое решение, которое может прийти в голову — это с помощью метода map
собрать другой массив, массив ростов этих людей, а потом с помощью метода reduce
посчитать их сумму и поделить на количество.
const averageHeight = people
.map(it => it.height)
.reduce((acc, it) => acc + it) / people.length;
Но это решение будет неэффективным, потому что вы будете использовать два прохода по массиву, вместо одного. Вы могли бы сразу использовать reduce
для того, чтобы сложить сразу все показатели по росту.
const averageHeight = people
.reduce((acc, it) => acc + it.height) / people.length;
На деле оценка эффективности алгоритмов это немного более сложная тема, она учитывает и какой алгоритм вы используете и объём входных данных, но направление мысли вы поняли. Умение оценить эффективность алгоритмов поможет вам писать код, который будет хорошо работать или на старых телефонах и компьютерах или который не будет тормозить при работе со сложными алгоритмами, например, с большими визуализациями.
Итого: учите всё подряд, что попадётся вам под руку. Для начала изучите дискретку, потому что она будет вашим основным инструментом в работе, а потом сосредоточьтесь на задачах вашего бизнеса и вы откроете для себя очень много нового в бизнесе, математике, строительстве и медицине.
Вопросы можно задавать здесь.
Метки: author htmlacademy разработка веб-сайтов javascript блог компании html academy для начинающих для новичков web- программирование веб-разработка |
Обмен информацией о кибер-угрозах в реальном времени: новая платформа от ServiceNow |
Метки: author it-guild информационная безопасность блог компании ит гильдия ит гильдия servicenow trusted security circles |
Анализируем требования рынка для data scientist |
def get_list_id_vacancies(area, text):
url_list = 'https://api.hh.ru/vacancies'
list_id = []
params = {'text': text, 'area': area}
r = requests.get(url_list, params=params)
found = json.loads(r.text)['found']; #кол-во всего найденных вакансий
if found <= 500: # API не отдает больше 500 вакансий за раз (на странице). Если найденно меньше 500 то получим все сразу.
params['per_page'] = found
r = requests.get(url_list, params=params)
data = json.loads(r.text)['items']
for vac in data:
list_id.append(vac['id'])
else:
i = 0;
while i <= 3: # если больше 500 то "перелистываем" страницы с 0 по 3 и получаем все вакансии поочереди. API не отдаст вам больше 2000 вакансий, поэтому тут захардкожено 3.
params['per_page'] = 500
params['page'] = i
r = requests.get(url_list, params=params)
if 200 != r.status_code:
break
data = json.loads(r.text)['items']
for vac in data:
list_id.append(vac['id'])
i += 1
return list_id
def get_vacancy(id):
url_vac = 'https://api.hh.ru/vacancies/%s'
r = requests.get(url_vac % id)
return json.loads(r.text)
{
"alternate_url": "https://hh.ru/vacancy/22285538",
"code": null,
"premium": false,
"description": "Мы занимаемся....",
"schedule": {
"id": "fullDay",
"name": "Полный день"
},
"suitable_resumes_url": null,
"site": {
"id": "hh",
"name": "hh.ru"
},
"billing_type": {
"id": "standard_plus",
"name": "Стандарт+"
},
"published_at": "2017-09-05T11:43:08+0300",
"test": null,
"accept_handicapped": true,
"experience": {
"id": "noExperience",
"name": "Нет опыта"
},
"address": {
"building": "36с7",
"city": "Москва",
"description": null,
"metro": {
"line_name": "Калининская",
"station_id": "8.470",
"line_id": "8",
"lat": 55.736478,
"station_name": "Парк Победы",
"lng": 37.514401
},
"metro_stations": [
{
"line_name": "Калининская",
"station_id": "8.470",
"line_id": "8",
"lat": 55.736478,
"station_name": "Парк Победы",
"lng": 37.514401
}
],
"raw": null,
"street": "Кутузовский проспект",
"lat": 55.739068,
"lng": 37.525432
},
"key_skills": [
{
"name": "Математическое моделирование"
},
{
"name": "Анализ рисков"
}
],
"allow_messages": true,
"employment": {
"id": "full",
"name": "Полная занятость"
},
"id": "22285538",
"response_url": null,
"salary": {
"to": 90000,
"gross": false,
"from": 50000,
"currency": "RUR"
},
"archived": false,
"name": "Математик/ Data scientist",
"contacts": null,
"employer": {
"logo_urls": {
"90": "https://hhcdn.ru/employer-logo/1680554.png",
"240": "https://hhcdn.ru/employer-logo/1680555.png",
"original": "https://hhcdn.ru/employer-logo-original/309546.png"
},
"vacancies_url": "https://api.hh.ru/vacancies?employer_id=1475513",
"name": "Аналитическое агентство Скориста",
"url": "https://api.hh.ru/employers/1475513",
"alternate_url": "https://hh.ru/employer/1475513",
"id": "1475513",
"trusted": true
},
"created_at": "2017-09-05T11:43:08+0300",
"area": {
"url": "https://api.hh.ru/areas/1",
"id": "1",
"name": "Москва"
},
"relations": [],
"accept_kids": false,
"response_letter_required": false,
"apply_alternate_url": "https://hh.ru/applicant/vacancy_response?vacancyId=22285538",
"quick_responses_allowed": false,
"negotiations_url": null,
"department": null,
"branded_description": null,
"hidden": false,
"type": {
"id": "open",
"name": "Открытая"
},
"specializations": [
{
"profarea_id": "14",
"profarea_name": "Наука, образование",
"id": "14.91",
"name": "Информатика, Информационные системы"
},
{
"profarea_id": "14",
"profarea_name": "Наука, образование",
"id": "14.141",
"name": "Математика"
}]
}
{
"description": "Мы занимаемся....",
"schedule": {
"id": "fullDay",
"name": "Полный день"
},
"accept_handicapped": true,
"experience": {
"id": "noExperience",
"name": "Нет опыта"
},
"key_skills": [
{
"name": "Математическое моделирование"
},
{
"name": "Анализ рисков"
}
],
"employment": {
"id": "full",
"name": "Полная занятость"
},
"id": "22285538",
"salary": {
"to": 90000,
"gross": false,
"from": 50000,
"currency": "RUR"
},
"name": "Математик/ Data scientist",
"employer": {
"name": "Аналитическое агентство Скориста",
},
"area": {
"name": "Москва"
},
"specializations": [
{
"profarea_id": "14",
"profarea_name": "Наука, образование",
"id": "14.91",
"name": "Информатика, Информационные системы"
},
{
"profarea_id": "14",
"profarea_name": "Наука, образование",
"id": "14.141",
"name": "Математика"
}]
}
def get_salary(vac): #зарплата не всегда заполена. Поэтому при обращение внутрь будет ошибка, для этого пишем отдельную функцию, которая вернет словарь с None, если данные пустые.
if vac['salary'] is None:
return {'currency':None , 'from':None,'to':None,'gross':None}
else:
return {'currency':vac['salary']['currency'],
'from':vac['salary']['from'],
'to':vac['salary']['to'],
'gross':vac['salary']['gross']}
def get_connection():
conn = pymysql.connect(host='localhost', port=3306, user='root', password='-', db='hh', charset="utf8")
return conn
def close_connection(conn):
conn.commit()
conn.close()
def insert_vac(conn, vac, text):
a = conn.cursor()
salary = get_salary(vac)
print(vac['id'])
a.execute("INSERT INTO vacancies (id, name_v, description, code_hh, accept_handicapped, \
area_v, employer, employment, experience, salary_currency, salary_from, salary_gross, \
salary_to, schedule_d, text_search) \
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
(vac['id'], vac['name'], vac['description'],
vac['code'], vac['accept_handicapped'], vac['area']['name'],
vac['employer']['name'],
vac['employment']['name'], vac['experience']['name'], salary['currency'],
salary['from'], salary['gross'],
salary['to'], vac['schedule']['name'], text))
for key_skill in vac['key_skills']:
a.execute("INSERT INTO key_skills(vacancy_id, name) VALUES(%s, %s)",(vac['id'], key_skill['name']))
for spec in vac['specializations']:
a.execute("INSERT INTO specializations(vacancy_id, name, profarea_name) VALUES(%s, %s, %s)",
(vac['id'], spec['name'], spec['profarea_name']))
a.close()
text_search = 'data scientist'
list_id_vacs = get_list_id_vacancies(text_search)
vacs = []
for vac_id in list_id_vacs:
vacs.append(get_vacancy(vac_id))
conn = get_connection()
for vac in vacs:
insert_vac(conn, vac, text_search)
close_connection(conn)
def get_vac_descriptions(conn, text_search):
a = conn.cursor()
a.execute("SELECT description FROM vacancies WHERE text_search = %s", text_search)
descriptions = a.fetchall()
a.close
return descriptions
def get_popular_phrase(text, len, count_phrases):
phrase_counter = Counter()
words = nltk.word_tokenize(text.lower())
for phrase in nltk.ngrams(words, len):
if all(word not in string.punctuation for word in phrase):
phrase_counter[phrase] += 1
return phrase_counter.most_common(count_phrases)
descriptions = get_vac_descriptions(get_connection(), 'data scientist')
text = ''
for description in descriptions:
text = text + description[0]
result = get_popular_phrase(text, 1, 20)
for r in result:
print(" ".join(r[0]) + " - " + str(r[1]))
def main():
descriprions = get_vac_descriptions(get_connection(), 'data scientist')
text = ''
for descriprion in descriprions:
text = text + descriprion[0]
result = get_popular_phrase(text, 4, 20, stopwords)
for r in result:
print(" ".join(r[0]) + " - " + str(r[1]))
main()
def get_stopwords():
descriptions = get_vac_descriptions(get_connection(), 'повар') \
+ get_vac_descriptions(get_connection(), 'уборщица') + \
get_vac_descriptions(get_connection(), 'слесарь')
text = ''
for description in descriptions:
text = text + descriprion[0]
stopwords = []
list = get_popular_phrase(text, 1, None, 200) #размер списка стоп слов
for i in list:
stopwords.append(i[0][0])
return stopwords
for description in descriptions:
if detect(description[0]) != 'en':
text = text + description[0]
Метки: author alexmikh python data mining data science hh.ru tutorial |
Использование различных метрик для кластеризации ключевых запросов |
Метки: author CalltouchForever повышение конверсии контекстная реклама веб-аналитика блог компании calltouch calltouch аналитика колтач кластеризация пулинг |
Как соответствовать требованиям к системе менеджмента качества по ISO 9001:2015 |
|
Вводим рейтинг участника Dribbble и Behance на «Моём круге» |
|
Обзор одной российской RTOS, часть 4. Полезная теория |
String output;
if (cnt > 0)
output = ',';
output += "{\"type\":\"";
output += (entry.isDirectory()) ? "dir" : "file";
output += "\",\"name\":\"";
output += entry.name();
output += "\"";
output += "}";
char xml [768];
...
xml[0] = 0;
if (cnt > 0)
{
strcat (xml,",");
}
strcat (xml,"{\"type\":\"");
if (entry.isSubDir())
{
strcat (xml,"dir");
} else
{
strcat (xml,"file");
}
strcat (xml,"\",\"name\":\"");
entry.getName(xml+strlen(xml),255);
strcat (xml,"\"}");
;;;723 static void _CopyRect(int LayerIndex, int x0, int y0, int x1, int y1, int xSize, int ySize) {
000000 e92d4ff0 PUSH {r4-r11,lr}
000004 b087 SUB sp,sp,#0x1c
;;;728 BufferSize = _GetBufferSize(LayerIndex);
000016 4620 MOV r0,r4
000018 f7fffffe BL _GetBufferSize
00001c 9006 STR r0,[sp,#0x18]
;;;730 SrcAddr = Offset + (y0 * _xSize[LayerIndex] + x0) * _BytesPerPixel[LayerIndex];
000030 4816 LDR r0,|L8.140|
000032 f8500024 LDR r0,[r0,r4,LSL #2]
000036 fb076000 MLA r0,r7,r0,r6
00003a 4915 LDR r1,|L8.144|
00003c 5d09 LDRB r1,[r1,r4]
00003e fb005001 MLA r0,r0,r1,r5
000042 9005 STR r0,[sp,#0x14]
00007e b007 ADD sp,sp,#0x1c
000080 e8bd8ff0 POP {r4-r11,pc}
Метки: author EasyLy программирование микроконтроллеров осрв макс rtos |
[Из песочницы] Как написать свой первый Linux device driver |
struct scull_dev {
struct scull_qset *data; /* Указатель на первый кусок памяти */
int quantum; /* Размер одного кванта памяти */
int qset; /* Количество таких квантов */
unsigned long size; /* Размер используемой памяти */
struct semaphore sem; /* Используется семафорами */
struct cdev cdev; /* Структура, представляющая символьные устройства */
};
struct scull_dev *scull_device;
struct scull_qset {
void **data;
struct scull_qset *next;
};
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.read = scull_read,
.write = scull_write,
.open = scull_open,
.release = scull_release,
};
module_init(scull_init_module);
module_exit(scull_cleanup_module);
int scull_major = 0; /* MAJOR номер*/
int scull_minor = 0; /* MINOR номер*/
int scull_nr_devs = 1; /* Количество регистрируемых устройств */
int scull_quantum = 4000; /* Размера памяти в байтах */
int scull_qset = 1000; /* Количество квантов памяти */
#include module.h> /* Содержит функции и определения для динамической загрузки модулей ядра */
#include init.h> /* Указывает на функции инициализации и очистки */
#include fs.h> /* Содержит функции регистрации и удаления драйвера */
#include cdev.h> /* Содержит необходимые функции для символьного драйвера */
#include slab.h> /* Содержит функцию ядра для управления памятью */
#include uaccess.h> /* Предоставляет доступ к пространству пользователя */
static int scull_init_module(void)
{
int rv, i;
dev_t dev;
rv = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
scull_major = MAJOR(dev);
if (rv) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return rv;
}
scull_device = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_device) {
rv = -ENOMEM;
goto fail;
}
memset(scull_device, 0, scull_nr_devs * sizeof(struct scull_dev));
for (i = 0; i < scull_nr_devs; i++) {
scull_device[i].quantum = scull_quantum;
scull_device[i].qset = scull_qset;
sema_init(&scull_device[i].sem, 1);
scull_setup_cdev(&scull_device[i], i);
}
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
return 0;
fail:
scull_cleanup_module();
return rv;
}
rv = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
scull_major = MAJOR(dev);
if (rv) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return rv;
}
scull_device = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_device) {
rv = -ENOMEM;
goto fail;
}
memset(scull_device, 0, scull_nr_devs * sizeof(struct scull_dev));
for (i = 0; i < scull_nr_devs; i++) {
scull_device[i].quantum = scull_quantum;
scull_device[i].qset = scull_qset;
sema_init(&scull_device[i].sem, 1);
scull_setup_cdev(&scull_device[i], i);
}
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
return 0;
fail:
scull_cleanup_module();
return rv;
}
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding scull %d", err, index);
}
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major, scull_minor);
if (scull_device) {
for (i = 0; i < scull_nr_devs; i++) {
scull_trim(scull_device + i);
cdev_del(&scull_device[i].cdev);
}
kfree(scull_device);
}
unregister_chrdev_region(devno, scull_nr_devs);
}
#include module.h>
#include init.h>
#include fs.h>
#include cdev.h>
#include slab.h>
#include uaccess.h>
int scull_major = 0;
int scull_minor = 0;
int scull_nr_devs = 1;
int scull_quantum = 4000;
int scull_qset = 1000;
struct scull_qset {
void **data;
struct scull_qset *next;
};
struct scull_dev {
struct scull_qset *data;
int quantum;
int qset;
unsigned long size;
unsigned int access_key;
struct semaphore sem;
struct cdev cdev;
};
struct scull_dev *scull_device;
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset;
int i;
for (dptr = dev->data; dptr; dptr = next) {
if (dptr->data) {
for (i = 0; i < qset; i++)
kfree(dptr->data[i]);
kfree(dptr->data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
struct file_operations scull_fops = {
.owner = THIS_MODULE,
//.read = scull_read,
//.write = scull_write,
//.open = scull_open,
//.release = scull_release,
};
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding scull %d", err, index);
}
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major, scull_minor);
if (scull_device) {
for (i = 0; i < scull_nr_devs; i++) {
scull_trim(scull_device + i);
cdev_del(&scull_device[i].cdev);
}
kfree(scull_device);
}
unregister_chrdev_region(devno, scull_nr_devs);
}
static int scull_init_module(void)
{
int rv, i;
dev_t dev;
rv = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
scull_major = MAJOR(dev);
if (rv) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return rv;
}
scull_device = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_device) {
rv = -ENOMEM;
goto fail;
}
memset(scull_device, 0, scull_nr_devs * sizeof(struct scull_dev));
for (i = 0; i < scull_nr_devs; i++) {
scull_device[i].quantum = scull_quantum;
scull_device[i].qset = scull_qset;
sema_init(&scull_device[i].sem, 1);
scull_setup_cdev(&scull_device[i], i);
}
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
printk(KERN_INFO "scull: major = %d minor = %d\n", scull_major, scull_minor);
return 0;
fail:
scull_cleanup_module();
return rv;
}
MODULE_AUTHOR("Your name");
MODULE_LICENSE("GPL");
module_init(scull_init_module);
module_exit(scull_cleanup_module);
Метки: author Alexeynew разработка под linux linux drivers |
[Перевод] Удивительно полезный инструмент: lsof |
Метки: author ru_vds системное администрирование настройка linux блог компании ruvds.com администрирование отладка linux lsof |
[recovery mode] Как мы делали небольшую охранную систему на RPi. Часть 2 |
print json_encode(getRequest($_POST));
*/5 * * * * php /aliceserver/checkAlice.php
include_once $dir . 'defines.php';
include_once $dir . 'db.php';
$db = new DataBase;
$db->connect();
$query = "SELECT alice, UNIX_TIMESTAMP(online) online FROM " . SQLTableStatus;
$result = $db->query($query);
$alice_is_on = $result[0]['alice'];
$online = (time() - $result[0]['online'] < 5*60);
if ($alice_is_on and !$online) {
include_once $dir . 'send_push.php';
$text = "Alice offline!";
$query = "INSERT INTO `" . SQLTableLog . "` (text, sender) VALUES ('$text', 1)";
$db->query($query);
send_push_message($text, AppToken, 'alice_offline.caf', $dir);
}
$db->disconnect();
Метки: author iBlacksus php mysql raspberry pi ios objective-c охранная система |
Нужна помощь в тестировании поддержки префикса таблиц в плигине Yii2 support для PhpStorm / Intellij |
Метки: author olegl84 yii yii2 |
Ежемесячная рубрика «Читаем статьи за Вас». Август 2017 |
Привет, Хабр!
С этого выпуска мы начинаем хорошую традицию: каждый месяц будет выходить набор рецензий на некоторые научные статьи от членов сообщества Open Data Science из канала #article_essence. Хотите получать их раньше всех — вступайте в сообщество ODS!
Статьи выбираются либо из личного интереса, либо из-за близости к проходящим сейчас соревнованиям. Если вы хотите предложить свою статью или у вас есть какие-то пожелания — просто напишите в комментариях и мы постараемся всё учесть в дальнейшем.
Статьи на сегодня:
Оригинал статьи
Автор: kt{at}ut{dot}ee
Это рецензия на статью, в которой диплернингом предсказываются финансовые рынки. Автор поста (далее — ZHD) указывает на следующие очевидные глупости данной и схожих статей:
Оригинал статьи
Код
Автор: kt{at}ut{dot}ee
Трансфер лернинг в наше время — стандартный прием. Взять сетку, натренированную на породах болонок из ImageNet, и прикрутить её куда-то для распознавания типов соплей — это уже стандартная программа для всех мамкиных диплёрнеров. В контексте обработки текста, трансфер лернинг обычно менее глубокий и упирается в использование заготовленных векторов слов типа Word2Vec, GloVe итд.
Авторы статьи предлагают углубить текстовый трансфер лернинг на один уровень следующим образом:
Далее авторы наворачивают какую-то нетривиальную модель c biattention и maxout (видимо, завалялась с их прошлой работы) и сравнивают, как она работает в разных задачах, если ей на входе скормить случайный эмбеддинг, GloVe, GloVe+CoVe, GloVe+CoVe+CharNGramEmbeddings.
По результатам кажется, что добавление CoVe повышает точность голого GloVe примерно на 1%. Иногда эффект меньше, иногда отрицательный, иногда добавление вместо CoVe в модель CharNGrams работает так же или даже лучше. В любом случае, совмещение GloVe+CoVe+CharNGrams работает точно лучше всех других методов.
На мой взгляд, из-за того, что авторы навернули нехилую модель с аттеншеном поверх сравниваемых типов эмбеддинга (GloVe vs CoVe), замер эффекта полезности CoVe получился излишне зашумленным и не очень убедительным. Я бы предпочел увидеть более "лабораторный" замер.
Оригинал статьи
Сайт
Автор: kt{at}ut{dot}ee
Существует сайт Getchu, на котором собраны "профили" анимешных героев различных японских игр с картинками-иллюстрациями. Эти картинки можно скачать.
Для нахождения лица на картинке можно использовать некую тулзу "lbpcascade animeface". Таким образом авторы получили 42к анимешных лиц, которые они потом ручками пересмотрели и выкинули 4% плохих примеров.
Имеется некая уже готовая CNN-модель Illustration2Vec, которая умеет распознавать на анимешных картинках свойства типа "улыбка", "цвет волос", итд. Авторы использовали её для того, чтобы отлейблить картинки и выбрали 34 интересующих их тэга.
Авторы запихнули это всё в DRAGAN (Kodali et al, где это отличается от обычных GAN авторы не углубляются, видимо, непринципиально).
Чтобы уметь генерировать картинки с заданными атрибутами, авторы делают как в ACGAN:
И генератор, и дискриминатор — довольно замороченные конволюционные SRResNet-ы (у генератора 16-блоковый, у дискриминатора 10-блоковый). У дискриминатора авторы убрали батчнорм-слои "since it would bring correlations within the mini-batch, which is undesired for the computation of the gradient norm." Я не очень понял эту проблему, поясните если вдруг кому ясно.
Тренировалось всё Адамом с уменьшающимся lr начиная от 0.0002, не очень понятно как долго.
Для вебаппа авторы сконвертировали сеть под WebDNN (https://github.com/mil-tokyo/webdnn) и поэтому генерят все картинки прямо в браузере на клиенте (!).
[Оригинал статьи](http://dl.acm.org/citation.cfm?id=3080673, https://doi.org/10.1145/3077136.3080673)
Статья — победитель Best Short Paper Awart SIGIR 2017
Автор: zevsone
Предложена принципиально новая система (LiveMaps) для анализа изображений карт и извлечения релевантного viewport'a.
Система позволяет делать аннотации для изображений, полученных с помощью поискового движка, позволяя пользователям переходить по ссылке, которая открывает интерактивную карту с центром в локации, соответствующей найденному изображению.
LiveMaps работает в несколько приемов. Сперва проверяется является ли изображение картой.
Если "да", система пытается определить геолокацию этого изображения. Для определения локации используется текстовая и визуальная информация извлеченная из изображения. В итоге, система создает интерактивную карту, отображающую географическую область, вычисленную для изображения.
Результаты оценки на датасете локаций с высоким рангом показывают, что система способна конструировать очень точные интерактивные карты, достигая при этом хорошего покрытия.
P.S. Совсем не ожидал получить best short paper award на такой маститой конфе (121 short paper в претендентах в этом году, все industry гиганты).
Оригинал статьи
Автор: egor.v.panfilov{at}gmail{dot}com
Статья посвящена исследованию одного из простейших методов аугментации изображений — Random Erasing (по-русски, закрашиванию случайных прямоугольников).
Аугментация параметризовалась 4-мя параметрами: (P_prob) вероятность применения к каждой картинке батча, (P_area) размер региона (area ratio), (P_aspect) соотношение сторон региона (aspect ratio), (P_value) заполняемое значением: случайные / среднее на ImageNet / 0 / 255.
Авторы провели оценку влияния данного метода аугментации на 3-х задачах: (A) object classification, (B) object detection, © person re-identification.
(A): Использовались 6 архитектур, начиная с AlexNet, заканчивая ResNeXt. Датасет — CIFAR10/100. Оптимальное значение параметров: P_prob = 0.5, P_aspect = в широком диапазоне, но желательно не 1(квадрат), P_area = 0.02-0.4 (2-40% изображения), P_value = random или среднее на ImageNet, для 0 и 255 результаты существенно хуже. Сравнили также с другими методами и аугментации (random cropping, random flipping), и регуляризации (dropout, random noise): в порядке снижения эффективности — всё вместе, random cropping, random flipping, random erasing. Drouput и random noise данный метод уделывет "в щепки". В целом, метод не самый мощный, но при оптимальных параметрах стабильно даёт 1% точности (5.5% -> 4.5%). Также пишут, что он повышает робастность классификатора к перекрытиям объектов :you-dont-say:.
(B): Использовалась Fast-RCNN на PASCAL VOC 2007+2012. Реализовали 3 схемы: IRE (Image-aware Random Erasing, также выбираем регион для зануления вслепую), ORE (Object-aware, зануляем только части bounding box'ов), I+ORE (и там, и там). Существенной разницы по mAP между этими методами нет. По сравнению с чистым Fast-RCNN, дают порядка 5% (67->71 на VOC07, 70->75 на VOC07+12), столько же, сколько и A-Fast-RCNN. Оптимальные параметры — P_prob = 0.5, P_area = 0.02-0.2 (2-20%), P_aspect = 0.3-3.33 (от лежачей до стоячей полоски).
©: Использовались ID-discim.Embedding (IDE), Triplet Net, и SVD-Net (все на базе ResNet, предобученной на ImageNet) на Market-1501 / DukeMTMC-reID / CUHK03. На всех моделях и датасетах аугментация стабильно даёт не менее 2% (до 8%) для Rank@1, и не менее 3% (до 8%) для mAP. Параметры те же, что и для (B).
В целом, несмотря на простоту метода, исследование и статья довольно аккуратные и подробные (10 страниц), с кучей графиков и таблиц. Порадовали китайцы, ничего не скажешь.
Оригинал статьи
Дополнительный материал про momentum
Автор: Arech
Давно я её читал, поэтому очень-очень поверхностно: после вдумчивого курения свойств классического моментума (он же моментум Бориса Поляка), на одномерных строго выпуклых квадратичных целях чуваки вывели (или нашли у кого-то?) неравенство, связывающее коэффициенты learning rate и моментума так, чтобы они попадали в некий "робастный" регион, гарантирующий наибыстрейшее схождение алгоритма SGD. Потом показали(?), что оное утверждение в принципе может как-то выполняться и для некоторых невыпуклых функций, по крайней мере в некоторой их локальной области, которую можно более-менее апроксимировать квадратичным приближением. После чего решили запилить свой тюнер YellowFin, который на основании знания предыдущей истории изменения градиента, апроксимировал бы нужные для неравенства характеристики поверхности функции ошибки (дисперсия градиента, некая "обощённая" кривизна поверхности и расстояние до локального минимума квадратичной апроксимации текущей точки) и, с помощью этих апроксимаций, выдавал бы подходящие значения learning rate и моментума для использования в SGD.
Также, изучив вопрос асинхронной (распределённой) тренировки сетей, чуваки предложили обобщение своего метода (Closed-loop YellowFin), которое учитывает, что реальный моментум в таких условиях оказывается больше запланированного.
Тестировали свёрточные 110- и 164-слойные ResNet на CIFAR10 и 100 соответственно, и какие-то LSTM на PTB, TS и WSJ. Результаты интересные (от х1.18 до х2.8 ускорения относительно Адама), но, как обычно, есть вопросы к постановке эксперимента — грубый подбор коэффициентов для компетиторов, + емнип, только один прогон на каждую архитектуру, + показаны результаты только тренировочного набора… Короче, есть к чему докопаться...
Как-то так, надеюсь, по деталям не сильно наврал.
Я подумывал запилить его к своей либине, но крепко влип в Self-Normalizing Neural Networks (SELU+AlphaDropout), которыми занялся чуть раньше и которые оказались мне крайне полезны, поэтому пока руки не дошли. Слежу за тем тредом в Lesagne (https://github.com/Lasagne/Lasagne/issues/856 — у человека возникли проблемы с воспроизводством результатов) и вообще, надеюсь, что будет больше инфы по воспроизводству их результатов. Так что если кто попробует — делитесь чокак.
Оригинал статьи
Автор: ternaus
Вопрос с тем, какой UpSampling лучше для различных архитектур, где есть Decoder, теребит умы многих, особенно тех, кто борется за пятый знак после запятой в задачах сегментации, super resolution, colorization, depth, boundary detection.
Ребята из Гугла и UCL заморочились и написали статью, где эмпирическим путем решили проверить кто лучше и найти в этом логику.
Проверили — выяснилось, что разница есть, но логика не очень просматривается.
Для сегментации:
[1] Transposed Conv = Upsampling + Conv и который все яростно используют в Unet норм.
[2] добрасывание skiped connections, то есть, трансформация SegNet => Unet железобетонно добрасывает. Это интуитивно понятно, но тут есть циферки.
[3] Такое ощущение, что Separable Transposed, которое Transposed, но с меньшим числом параметоров для сегментации, работает лучше. Хотелось бы, чтобы народ в #proj_cars это проверил.
[4] Хитроумный Bilinear additive Upsampling, который они предложили, на сегментации работает примерно как [3]. Но это тоже в сторону коллектива из #proj_cars проверить
[5] Они добрасывают residual connections куда-то, что тоже в теории может что-то добавить, но куда именно — не очень понятно, и добрасывает очень неуверенно и не всегда.
Для задач сегментации они используют resnet 50 как base и потом сверху добавляют decoder.
Для задачи instance boundary detection они решили выбрать метрику, при которой меньше оверфитишь на алгоритм разметки + циферки побольше получаются.
То есть, During the evaluation, predicted contour pixels within three from ground truth pixels are assumed to be correct
. Что сразу ставит вопрос о том, как это все переносится на задачи, где важен каждый пиксель. (Тут вспоминается Костин трюк со спутников для поиска заборов толщиной в один пиксель и то, как народ борется за +-1 пиксель на границах в задаче про машинки)
[6] У всех сетей, что они тренируют, у весов используется L2 регуляризация порядка 0.0002
Карпатый, вроде, тоже в свое время говорил, что всегда так делает для более стабильной сходимости. (Это мне надо попробовать, если кто так делает и это дает что-то заметное, было бы неплохо рассказать об этом в тредике)
Summary:
[1] Вопрос о том, кто и когда лучше они подняли, но не ответили.
[2] Они предложили еще один метод делать Upsampling, который работает не хуже других.
[3] Подтвердили, что skipped connection однозначно помогают, а residual — в зависимости от фазы луны.
Ждем месяц, о том, что скажет человеческий GridSearch на #proj_cars.
Оригинал статьи
Автор: egor.v.panfilov{at}gmail{dot}com
Эпиграф: Лавры китайцев не дают покоя никому, даже на чёрном континенте. Хороших компьютеров, правда, пока не завезли, но Тернаус велел писать, "и вот они продолжают".
Авторы попытались провести бенчмарк методов аугментации изображений на задаче классификации изображений, и выработать рекомендации по их использованию для различных случаев. В первом приближении, данные методы разделить на 2 категории: Generic (общеприменимые) и Complex (использующие информацию о домене / генеративные). В данной статье рассматриваются только Generic.
Все эксперименты в статье проводились на Caltech-101 (101 класс, 9144 изображения) с использованием ZFNet (половина информативной части статьи о том, как лучше всего обучить ванильную ZFNet). Обучали 30 эпох, используя DL4j. Рассматриваемые методы аугментации: (1) без аугмен-ции, (2-4) геометрические: horizontal flipping, rotating (-30deg и +30deg), cropping (4 угловых кропа), (5-7) фотометрические: color jittering, edge enhacement (добавляем к изображению результат фильтра Собеля), fancy PCA (усиливаем principal компоненты на изображениях).
Результаты: к бейзлайну (top1/top5: 48.1%/64.5%) (a) flipping даёт +1/2%, но увеличивает разброс точности, (b) rotating даёт +2%, © cropping даёт +14%, (d) color jittering +1.5/2.5%, (d-e) edge enhancement и fancy PCA по +1/2%. Т.е. среди геометрических методов впереди cropping, среди фотометрических — color jittering. В заключении авторы пишут, что существенное улучшение точности при аугментации cropping'ом может быть связано с тем, что датасет получается в 4 раза больше исходного (сбалансировать-то не судьба). Из положительного — не забыли 5-fold кросс-валидацию при оценке точности моделей. Почему были выбраны конкретно эти методы аугментации среди других (в т.ч. более популярных) узнаем, видимо, в следующей статье.
Оригинал статьи
Автор: egor.v.panfilov{at}gmail{dot}com
Статья рассматривает проблему выского потребления ресурсов современными архитектурами DNN (в частности, CNN). Самый главный бич — это обращение к динамической памяти. Например, инференс сети с 1 млрд связей на 20Гц потребляет ~13Вт.
Авторы предлагают метод снижения числа активных нейронов и связей в сети — prunning. Работает следующим образом: (1) обучаем сеть на полном датасете, (2) маскируем связи с весами ниже определённого уровня, (3) дообучаем оставшиеся связи на полном датасете. Шаги (2) и (3) можно и нужно повторять несколько раз, так как агрессивный (за 1 подход) prunning показывает результаты немного хуже (для AlexNet на ImageNet, например, 5x против 9x). Хитрости: использовать L2-регуляризацию весов, при дообучении уменьшать dropout, уменьшать learning rate, прунить и дообучать CONV и FC слои раздельно, выкидывать мёртвые (non-connected) нейроны по результатам шага (2).
Эксперименты производились в Caffe с сетями Lenet-300-100, Lenet-5 на MNIST, AlexNet, VGG-16 на ImageNet. На MNIST: уменьшили число весов и FLOP'ов в 12 раз, а также обнаружили, что prunning проявляет свойство механизма attention (по краям режет больше). На ImageNet: AlexNet обучали 75 часов и дообучали 173 часа, VGG-16 прунили и дообучали 5 раз. По весам удалось сжать в 9 и 13 раз соответственно, по FLOP'ам — в 3.3 и 5 раз. Интересен профиль того, как прунятся связи: первый CONV-слой ужимается меньше чем в 2 раза, следующие CONV — в 3 и более (до 12), скрытые FC — в 10-20 раз, последний FC слой — в 4 раза.
В заключение, авторы приводят результаты сравнения различных методов prunning'а (с L1-, L2-регуляризацией, с дообучением и без, отдельно по CONV, отдельно по FC). Вкратце, есть лень прунить, то учите сеть с L1, и половину слоёв можно просто выкинуть. Если не лень — только L2, прунить и дообучать итеративно ~5 раз. И последнее, хранение весов в sparse виде у авторов давало overhead в ~16% — не то чтобы критично, когда у вас сеть в 10 раз меньше.
Оригинал статьи
Автор: kt{at}ut{dot}ee
Как известно, процесс поиска моделей в машинном обучении упирается в оптимизацию некой целевой функции потерь. Самая простая функция потерь — процент ошибок на тренинг-сете, но её оптимизировать сложно и результат статистически плох, поэтому на практике используют разные суррогатные лоссы: квадрат ошибки, минус логарифм вероятности, экспонента от минус скора, hinge loss и т.д. Все суррогатные лоссы являются монотонными функциями, штрафующими ошибки тем больше, чем больше величина ошибки. Любой лосс можно интерпретировать как вид распределения целевой переменной (например, квадрат ошибок соответствует гауссовому распределению).
Авторы работы обнаружили, что суррогатный лосс вида
loss(p, y) := -(1-p)^gamma log(p) if y == 1 else -p^gamma log(1-p)
ещё нигде не использовался и не публиковался. Зачем нужно использовать именно такой лосс, а не ещё какой-нибудь, и каков смысл подразумеваемого распределения, авторы не знают, но им он кажется прикольным, т.к. здесь есть лишний параметр gamma, с помощью которого можно как будто бы подкручивать величину штрафа "легким" примерам. Авторы назвали эту функцию "focal loss".
Авторы подобрали один набор данных и одну модель-нейросеть на которых, если подогнать значения параметра, в табличке результатов виден якобы некий положительный эффект от использования такого лосса вместо обычной кросс-энтропии (взвешенной по классам). На самом деле, большая часть статьи жует вопросы применения RetinaNet для детекции объектов, не сильно зависящие от выбора лосс-функции.
Статья обязательна к прочтению всем начинающим свой путь в академии, т.к. она прекрасно иллюстрирует то, как писать убедительно-смотрящиеся публикации, когда в голове вообще нет хороших идей.
Альтернативное мнение
Следи за руками: чуваки взяли один из стандартных, довольно сложных датасетов, на которых меряются в детекции, взяли простенькую сеть без особых наворотов (то из чего все другие уже пробовали выжать сколько могли), приложили свой лосс и получили сходу результат single model без мультскейла и прочих трюков выше чем все остальные на этом датасете, включая все сети в одну стадию и более продвинутые двухстадийные. Чтобы убедиться, что дело в лоссе, а не чём-то ещё, они попробовали другие варианты, которые до этого были крутыми и модными техниками — балансирование кросс-энтропии, OHEM и получили результат стабильно выше на своём. Покрутили параметры своего и нашли вариант, который работает лучше всех и даже попробовали чуток объяснить почему (там есть график, где видно что гамма ниже 2 даёт довольно гладкое распределение, а больше двух уж очень резко штрафует (даже при двух там практически полка, удивительно что работает)).
Конечно, можно было им начать сравнивать 40 вариантов сетей, с миллионом вариантов гиперпараметров и на всех известных датасетах, да ещё и кроссвалидацию 10 раз по 10 фолдов, но сколько бы это времени тогда заняло и когда была бы публикация готова и сколько бы там было разных идей?
Тут всё просто — изменили один компонент, получили результат превосходящий SoTA на конкретном датасете. Убедились, что результат вызван изменением, а не чем-то ещё. Fin.
Альтернативное мнение
Наверное, стоит добавить, что если бы статья позиционировалась как представление RetinaNet, она бы смотрелась, по-моему, совсем по-другому. Она ведь и построена на самом-то деле в основном как пример применения RetinaNet. Зачем в неё внезапно вставили упор на лосс и странный заголовок, мне лично непонятно. Объективных замеров, подтверждающих высказанные про этот лосс тезисы там всё-таки нет.
Может быть, например, RetinaNet планируется опубликовать в более серьезном формате и с другим порядком авторов, а это — просто продукт стороннего эксперимента, который тоже решили лишней публикацией оформить потому что студент ведь работал и молодец. В таком случае это опять же пример того, как высосать лишнюю статью из воздуха.
В любом случае, я для себя никак не могу вынести из этой статьи обещанный в заглавии и тексте тезис "вкручивайте такой лосс везде и будет вам счастье".
Тезис "RetinaNet хорошо работает на COCO (с любым лоссом, причем!)" я, при этом, вынести вполне могу.
Оригинал статьи
Код
Автор: movchan74
Авторы сосредоточились на проблеме классификации изображений с небольшим датасетом. Типичный подход в таком случае следующий: взять предобученную CNN на ImageNet и дообучить на своем датасете (или даже дообучить только классификационные полносвязный слои). Но при этом сеть быстро переобучается и достигает не таких значений точности, каких хотелось бы. Авторы предлагают использовать не только целевой датасет (target dataset, мало данных, далее буду называть датасет T), но и дополнительный исходный датесет (source dataset, далее буду называть датасет S) с большим количеством изображений (обычно ImageNet) и обучать мультитаск на двух датасетах одновременно (делаем после CNN две головы по одной на датасет).
Но как выяснили авторы, использование всего датасета S для обучения не очень хорошая идея, лучше использовать некоторое подмножество датасета S. И далее в статье предлагаются различные способы поиска оптимального подмножества.
Получаем следующий фреймворк:
Рассмотрим как предлагается выбирать подмножество датасета S. Авторы предлагают для каждого семпла из датасета T находить некоторое количество соседей из S и учиться только на них. Близость определяется как расстояние между гистограммами низкоуровневых фильтров AlexNet или фильтров Габора. Гистограммы берутся, чтобы не учитывать пространственную составляющую.
Объяснение, почему берутся низкоуровневые фильтры, приводится следующее:
Еще из особенностей поиска близких изображений:
Авторы попробовали разные сверточные слои AlexNet и фильтры Габора для поиска близких семплов, лучше всего получилось если использовать 1+2 сверточные слои из AlexNet.
Также авторы предложили итеративный способ подбора количества похожих семплов из датасета S для каджого семпла из T. Изначально берем некоторое заданое число ближайших соседей для каждого отдельного семпла из T. Затем прогоняем обучение, и если ошибка для семпла большая, то увеличиваем количество ближайших соседей для этого семпла. Как именно производится увеличение ближайших соседей понятно из уравнения 6.
Из особенностей обучения. При формировании батча случайно выбираем семплы из датасета T, и для каждого выбранного семпла берем одного из его ближайших соседей.
Проведены эксперименты на следующих датасетах: Stanford Dogs 120, Oxford Flowers 102, Caltech 256, MIT Indoor 67. На всех датасетах получены SOTA результаты. Получилось поднять точность классфикации от 2 до 10% в зависимости от датасета.
Оригинал статьи
Код
Автор: repyevsky{at}gmail{dot}com
Статья про мета-обучение: авторы хотят научить модель объединять свой предыдущий опыт с малым количеством новой информации для решения новых заданий из некоторого общего класса.
Чтобы было понятно чего хотят добиться авторы, расскажу как оценивается модель.
В качество бенчмарка для классификации используются два датасета: Omniglot и miniImagenet. В первом рукописные буквы из нескольких алфавитов — всего около 1600 классов, 20 примеров на класс. Во втором 100 классов из Imagenet — по 600 картинок на класс. Ещё есть раздел про RL, но его я не смотрел.
Перед обучением все классы разбиваются на непересекающиеся множества train
, validation
и test
. Для проверки, из test
-классов (которые модель не видела при обучении) выбирается, например, 5 случайных классов (5-way learning). Для каждого из выбранных классов сэмплится несколько примеров, лейблы кодируются one-hot вектором длины 5. Дальше примеры для каждого класса делятся на две части A
и B
. Примеры из A
показываются модели с ответами, а примеры из B
используются для проверки точности классификации. Так формируется задание. Авторы смотрят на accuracy
.
Таким образом, нужно научить модель адаптироваться к новому заданию (новому набору классов) за несколько итераций/новых примеров.
В отличии от предыдущих работ, где для этого пытались использовать RNN или feature embedding с непараметрическими методами на тесте (вроде k-ближайших соседей), авторы предлагают подход, позволяющий настроить параметры любой стандартной модели, если она обучается градиентными методами.
Ключевая идея: обновлять веса модели так, чтобы она давала лучший результат на новых заданиях.
Интуиция: внутри модели получатся универсальные для всех классов датасета представления входных данных, по которым модель сможет быстро подстроиться под новое задание.
Суть в следующем. Пусть мы хотим, чтобы наша модель F(x, p)
обучалась новому заданию за 1 итерацию (1-shot learning). Тогда для обучения нужно из тренировочных классов подготовить такие же задания как на тесте. Дальше на примерах из части A
мы считаем loss
, его градиент и делаем одну итерацию обучения — в итоге получаем промежуточные обновленные веса p' = p - a*grad
и новую версию модели — F(x, p')
. Считаем loss
для F(x, p')
на B
и минимизируем его относительно исходных весов p
. Получаем настоящие новые веса — конец итерации. Когда считается градиент от градиента :xzibit:, появляются вторые производные.
На самом деле, генерируется сразу несколько заданий, объединеных в metabatch. Для каждого находится свой p'
и считается свой loss
. Потом все эти loss
суммируются в total_loss
, который уже минимизируется относительно p
.
Авторы применили свой подход к базовым моделям из предыдущих работ (маленькие сверточная и полносвязная сети) и получили SOTA на обоих датасетах.
При этом, итоговая модель получается без дополнительных параметров для мета-обучения. Зато используется большое количество вычислений, в том числе из-за вторых производных. Авторы пробовали отбросить вторые производные на miniImagenet. При этом accuracy
осталась почти такой же, а вычисления ускорились на 33%. Предположительно это связано с тем, что ReLU
— кусочно-линейная функция и вторая производная у неё почти всегда нулевая.
Код авторов на Tensorflow: https://github.com/cbfinn/maml. Там внутренний градиентый шаг делается вручную, а внешний с помощью AdamOptimizer.
За редактуру спасибо yuli_semenova.
|