-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51

Habrahabr/New








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/new/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-new-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

[Из песочницы] Автоматическое развертывание приложения с Maven и Wildfly

Суббота, 01 Июля 2017 г. 21:04 + в цитатник
Привет Хабр! Начать хочу с небольшой статьи мануала, как подружить WildFly с Maven.

Wildfly – Это ребрендинг и развитие JBoss AS7/EAP6 в области как администрирования, так и API для разработчика. Wildfly построен с использованием Java SE 7. Отлично интегрируется с основными Java IDE. Краткая цитата из статьи

Немного официальной информации о 10 версии


  • Реализация сертифицирована на соответствие Full- и Web-профилям Java EE 7. Код WildFly распространяется под лицензией LGPL;
  • В отличие от коммерческого продукта JBoss Enterprise Application Platform, позиционируемого как полностью протестированная и сертифицированная платформа Java EE, WildFly ориентирован, прежде всего, на продвижение технологий. WildFly выступает в роли upstream-проекта для коммерческого продукта JBoss Enterprise. В качестве основной области использования WildFly рассматривается разработка и быстрое внедрение прототипов.

Основные особенности релиза


  • Прекращена поддержка Java 7, что позволило обеспечить более глубокую интеграцию с Java 8 Runtime. Добавлена поддержка текущих снапшотов Java 9;
  • Поставка ActiveMQ Artemis в качестве брокера рассылки сообщений (Java Message Service Broker), совместимого на уровне протокола и заменившего собой HornetQ;
  • Поддержка запуска хост-контроллера при помощи CLI. Новая команда embed-host-controller позволяет редактировать содержимое файлов domain.xml и host.xml без запуска дополнительных процессов или открытия сетевых сокетов;
  • Поддержка JavaScript в http-сервере Undertow.io, позволяющая создавать на языке JavaScript серверные скрипты, которые могут обращаться к CDI Beans и JPA Entity Beans. Указанную возможность удобно использовать для создания внешних обвязок или REST-обработчиков. Отредактированный код JavaScript становится доступен сразу и не требует перезапуска приложения;
  • Поддержка одиночного отказоустойчивого развёртывания приложения («singleton deployment»), при котором в случае использования группы кластеризованных серверов развёртывание будет произведено только на одном узле, но в случае выхода этого узла из строя, приложение будет автоматически перенесено на другой узел;
  • Поддержка одиночного отказоустойчивого брокера рассылки сообщений (Singleton MDB), запускающего доставку только на одном узле, но в случае сбоя использующего для обработки сообщений другой узел;
  • Автоматический выбор размера пула SLSB и MDB, в зависимости от имеющихся системных ресурсов;
  • Средства для миграции устаревших подсистем, таких как jbossweb (AS 7.1), jacorb (WildFly 8) и hornetq (WildFly 9), которые автоматизируют преобразование старых конфигураций в эквиваленты, работающие в WildFly 10;
  • В реализации Hibernate 5 значительно улучшено качество байткода, внесены оптимизации производительности и добавлены улучшения в API.

Хочу рассказать об автоматическом деплое в Wildfly с помощью Maven, а так же рассказать кратко о запускe UI в том же самом контейнере.

Необходимо использовать Maven версии не ниже 3.3.9( использую maven 3.5.0 ), иначе Wildfly Plugin не будет работать.

Версия Wildfly, который на данный момент использую — 10.1.0-Final, версия плагина — 1.2.0.Alpha4

В Pom.XML собираемого приложения необходимо прописать данные для подключения к серверу Wildfly

Пример:

            
                org.wildfly.plugins
                wildfly-maven-plugin
                
                    ${wildfly-hostname}
                    ${wildfly-port}
                    ${wildfly-username}
                    ${wildfly-password}
                    ${wildfly-name}
                
            

Так же хорошей практикой может быть вынос настроек в settings.xml либо в pom.xml уровнем выше. Это позволит так же использовать профили при деплое – локальный деплой, деплой на прод.

Область профилей в settings.xml.

        
            localhost
            
                localhost
                9990
                admin
                admin
                core.war
            
        
        
            dev
            
                Prod_Server
                9990
                admin
                admin
                core-prod_vers.war
            
        

После всех настроек, с помощью maven, из командной строки, из IDE, из, например, Jenkins (maven plugin), можно будет деплоить war c помощью mvn wildfly:deploy, так же можно использовать mvn wildfly:undeploy и mvn wildfly:redeploy для удаления и передеплоя соответственно. Выбор профиля -Plocalhost позволит запускать с настройками из профиля с id localhost, -Pdev соответственно запускает для прода( все данные в настройках дефолтны или вымышлены).

При верной настройке в WildFly консоле в разделе deployments у Вас появится необходимый war.

Кроме этого, WildFly позволяет запускать UI.

Настройка WildFly для запуска UI:

Проект помещать в корень WildFLy в директорию, которую Вы сами выберете для Вашего UI.
В данном примере я использовал директорию UI.

В файле /PathToWildfly/standalone/configuration/standalone.xml необходимо добавить строки(строки с учетом соседних строк, для облегчения поиска):



    
    


    
    

После этого WildFLy автоматически подхватывает изменения и по адресу localhost:8080/UI будет доступно ваше приложение.

На данный момент запуск был осуществлен с помощью команды ./standalone.sh -b=0.0.0.0 -bmanagement=0.0.0.0, приложение работает на Linux Oracle в screen.

За время использования были выявлены 2 проблемы:

  1. Иногда перезапуск WildFLy затягивается и долго подтягивается localhost:9990 — консоль.
  2. При запуске из Jenkins на Maven 3.3.9 с тэгом redeploy происходит утекание памяти и приложение удаляет обе версии war и ругается на дубликат. (Воспроизводится не всегда)

Данная связка около 2 месяцев, кроме вышеописанных, проблем не встречал. Всем рулит Jenkins в данном случае, он стартует и mvn wildfly:undeploy mvn wildfly:deploy ( использую такую стратегию, т.к. redeploy не отрабатывает) и доставляет собранный UI в директорию UI, обновление происходит по ночам, полёт нормальный.

Так же тестировал запуск на win хосте, отличий не заметил в работе, все так же стабильно.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332086/


Метки:  

Автоэнкодеры в Keras, Часть 6: VAE + GAN

Суббота, 01 Июля 2017 г. 20:40 + в цитатник

Содержание



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

В этой части попробуем взять лучшее от обоих подходов путем совмещения вариационных автоэнкодеров (VAE) и генеративных состязающихся сетей (GAN).

Подход, который будет описан далее, основан на статье [Autoencoding beyond pixels using a learned similarity metric, Larsen et al, 2016].



Иллюстрация из [1]

Разберемся более подробно, почему восстановленные изображения получаются смазанные.


В части про VAE рассматривался процесс генерации изображений X из скрытых (latent) переменных Z.
Так как размерность скрытых переменных Z значительно ниже, чем размерность объектов X (в части про VAE эти размерности были 2 и 784), а также всегда присутствует некоторая случайность, то одному и тому же Z может соответствовать многомерное распределение X, то есть P(X|Z). Это распределение можно представить как:

P(X|Z) = f(Z) + \epsilon,

где f(Z) некоторый средний наиболее вероятный объект при заданном Z, а \epsilon — шум какой-то сложной природы.

Когда мы обучаем автоэнкодеры, мы сравниваем вход из выборки X_s и выход автоэнкодера \tilde X_s с помощью некоторого функционала ошибки L,

L(X_s, \tilde X_s), \\
\tilde X_s = f_d(Z; \theta_d), \\
Z \sim Q(Z|X_s; \theta_e),

где Q,\ f_d — энкодер и декодер.

Задавая L, мы определяем шум \epsilon_L, которым приближаем настоящий шум \epsilon.
Минимизируя L, мы учим автоэнкодер подстраиваться под шум \epsilon_L, убирая его, то есть находить среднее значение в заданной метрике (во второй части это показывалось наглядно на простом искусственном примере).

Если шум \epsilon_L, который мы определяем функционалом L, не соответствует реальному шуму \epsilon, то f_d(Z; \theta_2) окажется сильно смещенным от реального f(Z) (пример: если в регрессии реальный шум лаплассовский, а минимизируется разность квадратов, то предсказанное значение будет смещено в сторону выбросов).

Возвращаясь к картинкам: посмотрим, как связана попиксельная метрика, которой определен лосс в предыдущих частях, и метрика, используемая человеком. Пример и иллюстрация из [2]:



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

С точки зрения попиксельной метрики (а) намного ближе к (b), чем к (с); хотя с точки зрения человеческого восприятия (b) — даже не цифра, а вот разница между (а) и (b) практически незаметна.

Автоэнкодеры с попиксельной метрикой, таким образом, размазывали изображение, отражая тот факт, что
в рамках близких Z:
  • положение цифр слегка гуляет по картинке,
  • нарисованы цифры слегка по-разному (хотя попиксельно может быть значительно далеко).

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

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

Про GAN’ы написано в прошлой части.

Соединяя VAE и GAN


Генератор GAN выполняет функцию, аналогичную декодеру в VAE: оба сэмплят из априорного распределения P(Z) и переводят его в P_g(X). Однако роли у них разные: декодер восстанавливает объект, закодированный энкодером, при обучении опираясь на некоторую метрику сравнения; генератор же генерирует случайный объект, который ни с чем не сравнивается, лишь бы дискриминатор не мог отличить, какому из распределений P или P_g он принадлежит.

Идея: добавить в VAE третью сеть — дискриминатор и подавать ей на вход и восстановленный объект и оригинал, а дискриминатор обучать определять, какой из них какой.


Иллюстрация из [1]

Разумеется, использовать ту же самую метрику сравнения из VAE мы уже не можем, потому что, обучаясь в ней, декодер генерирует изображения, легко отличимые от оригинала. Не использовать метрику вообще — тоже, так как нам бы хотелось, чтобы воссозданный \tilde X был похож на оригинал, а не просто какой-то случайный из P(X), как в чистом GAN.

Задумаемся, однако, вот о чем: дискриминатор, учась отличать реальный объект от сгенерированного, будет вычленять какие-то характерные черты одних и других. Эти черты объекта будут закодированы в слоях дискриминатора, и на основе их комбинации он уже будет выдавать вероятность объекта быть реальным. Например, если изображение размыто, то какой-то нейрон в дискриминаторе будет активироваться сильнее, чем если оно четкое. При этом чем глубже слой, тем более абстрактные характеристики входного объекта в нем закодированы.

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

L(X_s, \tilde X_s) \longrightarrow L_d(d_l(X_s), d_l(\tilde X_s)) \\
\tilde X_s = f_d(Z; \theta_d), \\
Z \sim Q(X_s; \theta_e),

где d_l — активации на l-ом слое дискриминатора, а Q, \ f_d — энкодер и декодер.

При этом можно надеяться, что новая метрика L_d будет лучше.

Ниже приведена схема работы получившейся VAE+GAN сети, предлагаемая авторами [1].



Иллюстрация из [1]

Здесь:
  • X — входной объект из P(X),
  • Z_p — сэмплированный Z из P(Z),
  • X_p — объект сгенерированный декодером из Z_p,
  • \tilde X — объект восстановленный из X,
  • \mathcal L_{prior} = KL \left[ Q(Z|X)||P(Z) \right] — лосс, заставляющий энкодер переводить P(X) в нужное нам P(Z) (точно как в части 3 про VAE),
  • \mathcal L_{llike}^{Dis_l} = L_d(d_l(X), d_l(\tilde X)) — метрика между активациями l-ого слоя дискриминатора D на реальном X и восстановленным \tilde X = f_d(Q(X)),
  • \mathcal L_{GAN} = \log(D(X)) + \log(1 - D(f_d(Z))) + \log(1 - D(f_d(Q(X)))) — кросс-энтропия между реальным распределением лейблов настоящих/сгенерированных объектов, и распределением вероятности предсказываемым дискриминатором.

Как и в случае с GAN, мы не можем обучать все 3 части сети одновременно. Дискриминатор надо обучать отдельно, в частности, не нужно, чтобы дискриминатор пытался уменьшать \mathcal L_{llike}^{Dis_l}, так как это схлопнет разницу активаций в 0. Поэтому обучение всех сетей надо ограничить только на релевантные им лоссы.

Схема, предлагаемая авторами:

\theta_{Enc} = \theta_{Enc} - \Delta_{\theta_{Enc}} (\mathcal L_{prior} + \mathcal L^{Dis_l}_{llike}), \\
\theta_{Dec} = \theta_{Dec} - \Delta_{\theta_{Dec}} (\gamma \mathcal L^{Dis_l}_{llike} - \mathcal L_{GAN}), \\
\theta_{Dis} = \theta_{Dis} - \Delta_{\theta_{Dis}} (\mathcal L_{GAN})

Выше видно, на каких лоссах какие сети учатся. Особое внимание разве что стоит уделить декодеру: он, с одной стороны, пытается уменьшить расстояние между входом и выходом в метрике l-го слоя дискриминатора (\mathcal L^{Dis_l}_{llike}), а с другой, пытается обмануть дискриминатор (увеличивая \mathcal L_{GAN}). В статье авторы утверждают, что, меняя коэффициент \gamma, можно влиять на то, что важнее для сети: контент (\mathcal L^{Dis_l}_{llike}) или стиль (\mathcal L_{GAN}). Не могу, однако, сказать, что наблюдал этот эффект.

Код


Код во многом повторяет то, что было в прошлых частях про чистые VAE и GAN.

Опять же сразу будем писать conditional модель.

from IPython.display import clear_output
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns

from keras.layers import Dropout, BatchNormalization, Reshape, Flatten, RepeatVector
from keras.layers import Lambda, Dense, Input, Conv2D, MaxPool2D, UpSampling2D, concatenate
from keras.layers.advanced_activations import LeakyReLU
from keras.layers import Activation
from keras.models import Model, load_model


# Регистрация сессии в keras
from keras import backend as K
import tensorflow as tf
sess = tf.Session()
K.set_session(sess)


# Импорт датасета
from keras.datasets import mnist
from keras.utils import to_categorical
(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train = x_train.astype('float32') / 255.
x_test  = x_test .astype('float32') / 255.
x_train = np.reshape(x_train, (len(x_train), 28, 28, 1))
x_test  = np.reshape(x_test,  (len(x_test),  28, 28, 1))

y_train_cat = to_categorical(y_train).astype(np.float32)
y_test_cat  = to_categorical(y_test).astype(np.float32)


# Глобальные константы
batch_size = 64
batch_shape = (batch_size, 28, 28, 1)
latent_dim = 8
num_classes = 10
dropout_rate = 0.3
gamma = 1 # Коэффициент гамма


# Итераторы тренировочных и тестовых батчей
def gen_batch(x, y):
    n_batches = x.shape[0] // batch_size
    while(True):
        idxs = np.random.permutation(y.shape[0])
        x = x[idxs]
        y = y[idxs]
        for i in range(n_batches):
            yield x[batch_size*i: batch_size*(i+1)], y[batch_size*i: batch_size*(i+1)]

train_batches_it = gen_batch(x_train, y_train_cat)
test_batches_it  = gen_batch(x_test,  y_test_cat)


# Входные плейсхолдеры
x_ = tf.placeholder(tf.float32, shape=(None, 28, 28, 1),  name='image')
y_ = tf.placeholder(tf.float32, shape=(None, 10),         name='labels')
z_ = tf.placeholder(tf.float32, shape=(None, latent_dim), name='z')

img = Input(tensor=x_)
lbl = Input(tensor=y_)
z   = Input(tensor=z_)

Описание моделей от GAN отличается почти только добавленным энкодером.

def add_units_to_conv2d(conv2, units):
    dim1 = int(conv2.shape[1])
    dim2 = int(conv2.shape[2])
    dimc = int(units.shape[1])
    repeat_n = dim1*dim2
    units_repeat = RepeatVector(repeat_n)(lbl)
    units_repeat = Reshape((dim1, dim2, dimc))(units_repeat)
    return concatenate([conv2, units_repeat])


# у меня получалось, что батч-нормализация очень сильно тормозит обучение на начальных этапах (подозреваю, что из-за того, что P и P_g почти не ра)
def apply_bn_relu_and_dropout(x, bn=False, relu=True, dropout=True):
    if bn:
        x = BatchNormalization(momentum=0.99, scale=False)(x)
    if relu:
        x = LeakyReLU()(x)
    if dropout:
        x = Dropout(dropout_rate)(x)
    return x


with tf.variable_scope('encoder'):
    x = Conv2D(32, kernel_size=(3, 3), strides=(2, 2), padding='same')(img)
    x = apply_bn_relu_and_dropout(x)
    x = MaxPool2D((2, 2), padding='same')(x)

    x = Conv2D(64, kernel_size=(3, 3), padding='same')(x)
    x = apply_bn_relu_and_dropout(x)

    x = Flatten()(x)
    x = concatenate([x, lbl])
    
    h = Dense(64)(x)
    h = apply_bn_relu_and_dropout(h)

    z_mean    = Dense(latent_dim)(h)
    z_log_var = Dense(latent_dim)(h)

    def sampling(args):
        z_mean, z_log_var = args
        epsilon = K.random_normal(shape=(batch_size, latent_dim), mean=0., stddev=1.0)
        return z_mean + K.exp(K.clip(z_log_var/2, -2, 2)) * epsilon
    l = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var])
encoder = Model([img, lbl], [z_mean, z_log_var, l], name='Encoder')


with tf.variable_scope('decoder'):
    x = concatenate([z, lbl])
    x = Dense(7*7*128)(x)
    x = apply_bn_relu_and_dropout(x)
    x = Reshape((7, 7, 128))(x)
    x = UpSampling2D(size=(2, 2))(x)

    x = Conv2D(64, kernel_size=(5, 5), padding='same')(x)
    x = apply_bn_relu_and_dropout(x)

    x = Conv2D(32, kernel_size=(3, 3), padding='same')(x)
    x = UpSampling2D(size=(2, 2))(x)
    x = apply_bn_relu_and_dropout(x)
    
    decoded = Conv2D(1, kernel_size=(5, 5), activation='sigmoid', padding='same')(x)
decoder = Model([z, lbl], decoded, name='Decoder')


with tf.variable_scope('discrim'):
    x = Conv2D(128, kernel_size=(7, 7), strides=(2, 2), padding='same')(img)
    x = MaxPool2D((2, 2), padding='same')(x)
    x = apply_bn_relu_and_dropout(x)
    x = add_units_to_conv2d(x, lbl)

    x = Conv2D(64, kernel_size=(3, 3), padding='same')(x)
    x = MaxPool2D((2, 2), padding='same')(x)
    x = apply_bn_relu_and_dropout(x)

    # l-слой на котором будем сравнивать активации
    l = Conv2D(16, kernel_size=(3, 3), padding='same')(x)
    x = apply_bn_relu_and_dropout(x)

    h = Flatten()(x)
    d = Dense(1, activation='sigmoid')(h)
discrim = Model([img, lbl], [d, l], name='Discriminator')

Построение графа вычислений на основе моделей:

z_mean, z_log_var, encoded_img = encoder([img, lbl])

decoded_img = decoder([encoded_img, lbl])
decoded_z   = decoder([z,           lbl])

discr_img,     discr_l_img     = discrim([img,         lbl])
discr_dec_img, discr_l_dec_img = discrim([decoded_img, lbl])
discr_dec_z,   discr_l_dec_z   = discrim([decoded_z,   lbl])

cvae_model = Model([img, lbl], decoder([encoded_img, lbl]), name='cvae')
cvae =  cvae_model([img, lbl])

Определение лоссов:
Интересно, что получался слегка лучше результат, если в качестве метрики на активациях слоев брать не MSE, а кросс-энтропию.

# Базовые лоссы
L_prior = -0.5*tf.reduce_sum(1. + tf.clip_by_value(z_log_var, -2, 2) - tf.square(z_mean) - tf.exp(tf.clip_by_value(z_log_var, -2, 2)))/28/28

log_dis_img     = tf.log(discr_img + 1e-10)
log_dis_dec_z   = tf.log(1. - discr_dec_z + 1e-10)
log_dis_dec_img = tf.log(1. - discr_dec_img + 1e-10)

L_GAN = -1/4*tf.reduce_sum(log_dis_img + 2*log_dis_dec_z + log_dis_dec_img)/28/28

# L_dis_llike = tf.reduce_sum(tf.square(discr_l_img - discr_l_dec_img))/28/28
L_dis_llike = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(labels=tf.sigmoid(discr_l_img),
                                                                    logits=discr_l_dec_img))/28/28


# Лоссы энкодера, декодера, дискриминатора
L_enc = L_dis_llike + L_prior 
L_dec = gamma * L_dis_llike - L_GAN
L_dis = L_GAN


# Определение шагов оптимизатора
optimizer_enc = tf.train.RMSPropOptimizer(0.001)
optimizer_dec = tf.train.RMSPropOptimizer(0.0003)
optimizer_dis = tf.train.RMSPropOptimizer(0.001)

encoder_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, "encoder")
decoder_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, "decoder")
discrim_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, "discrim")

step_enc = optimizer_enc.minimize(L_enc, var_list=encoder_vars)
step_dec = optimizer_dec.minimize(L_dec, var_list=decoder_vars)
step_dis = optimizer_dis.minimize(L_dis, var_list=discrim_vars)


def step(image, label, zp):
    l_prior, dec_image, l_dis_llike, l_gan, _, _ = sess.run([L_prior, decoded_z, L_dis_llike, L_GAN, step_enc, step_dec],
                                                            feed_dict={z:zp, img:image, lbl:label, K.learning_phase():1})
    return l_prior, dec_image, l_dis_llike, l_gan

def step_d(image, label, zp):
    l_gan, _ = sess.run([L_GAN, step_dis], feed_dict={z:zp, img:image, lbl:label, K.learning_phase():1})
    return l_gan


Функции рисования картинок после и в процессе тренировки:

Код
digit_size = 28
def plot_digits(*args, invert_colors=False):
    args = [x.squeeze() for x in args]
    n = min([x.shape[0] for x in args])
    figure = np.zeros((digit_size * len(args), digit_size * n))

    for i in range(n):
        for j in range(len(args)):
            figure[j * digit_size: (j + 1) * digit_size,
                   i * digit_size: (i + 1) * digit_size] = args[j][i].squeeze()

    if invert_colors:
        figure = 1-figure

    plt.figure(figsize=(2*n, 2*len(args)))
    plt.imshow(figure, cmap='Greys_r')
    plt.grid(False)
    ax = plt.gca()
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    plt.show()


# Массивы, в которые будем сохранять результаты, для последующей визуализации
figs = [[] for x in range(num_classes)]
periods = []

save_periods = list(range(100)) + list(range(100, 1000, 10))

n = 15 # Картинка с 15x15 цифр
from scipy.stats import norm
# Так как сэмплируем из N(0, I), то сетку узлов, в которых генерируем цифры берем из обратной функции распределения
grid_x = norm.ppf(np.linspace(0.05, 0.95, n))
grid_y = norm.ppf(np.linspace(0.05, 0.95, n))
grid_y = norm.ppf(np.linspace(0.05, 0.95, n))
def draw_manifold(label, show=True):
    # Рисование цифр из многообразия
    figure = np.zeros((digit_size * n, digit_size * n))
    input_lbl = np.zeros((1, 10))
    input_lbl[0, label] = 1
    for i, yi in enumerate(grid_x):
        for j, xi in enumerate(grid_y):
            z_sample = np.zeros((1, latent_dim))
            z_sample[:, :2] = np.array([[xi, yi]])

            x_decoded = sess.run(decoded_z, feed_dict={z:z_sample, lbl:input_lbl, K.learning_phase():0})
            digit = x_decoded[0].squeeze()
            figure[i * digit_size: (i + 1) * digit_size,
                   j * digit_size: (j + 1) * digit_size] = digit
    if show:
        # Визуализация
        plt.figure(figsize=(15, 15))
        plt.imshow(figure, cmap='Greys')
        plt.grid(False)
        ax = plt.gca()
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)
        plt.show()
    return figure


# Рисование распределения z
def draw_z_distr(z_predicted):
    im = plt.scatter(z_predicted[:, 0], z_predicted[:, 1])
    im.axes.set_xlim(-5, 5)
    im.axes.set_ylim(-5, 5)
    plt.show()
    

def on_n_period(period):
    n_compare = 10

    clear_output() # Не захламляем output

    # Сравнение реальных и декодированных цифр
    b = next(test_batches_it)
    decoded = sess.run(cvae, feed_dict={img:b[0], lbl:b[1], K.learning_phase():0})
    plot_digits(b[0][:n_compare], decoded[:n_compare])

    # Рисование многообразия для рандомного y
    draw_lbl = np.random.randint(0, num_classes)    
    print(draw_lbl)
    for label in range(num_classes):
        figs[label].append(draw_manifold(label, show=label==draw_lbl))

    xs = x_test[y_test == draw_lbl]
    ys = y_test_cat[y_test == draw_lbl]
    z_predicted = sess.run(z_mean, feed_dict={img:xs, lbl:ys, K.learning_phase():0})
    draw_z_distr(z_predicted)
    
    periods.append(period)


Процесс обучения:

sess.run(tf.global_variables_initializer())

nb_step = 3 # Количество шагов во внутреннем цикле

batches_per_period = 3
for i in range(48000):
    print('.', end='')

    # Шаги обучения дискриминатора
    for j in range(nb_step):
        b0, b1 = next(train_batches_it)
        zp = np.random.randn(batch_size, latent_dim)
        l_g = step_d(b0, b1, zp)
        if l_g < 1.0:
            break
        
    # Шаг обучения декодера и энкодера
    for j in range(nb_step):
        l_p, zx, l_d, l_g = step(b0, b1, zp)
        if l_g > 0.4:
            break
        b0, b1 = next(train_batches_it)
        zp = np.random.randn(batch_size, latent_dim)

    # Периодическая визуализация результата
    if not i % batches_per_period:
        period = i // batches_per_period
        if period in save_periods:
            on_n_period(period)
        print(i, l_p, l_d, l_g)

Функция рисования гифок:

Код
from matplotlib.animation import FuncAnimation
from matplotlib import cm
import matplotlib


def make_2d_figs_gif(figs, periods, c, fname, fig, batches_per_period): 
    norm = matplotlib.colors.Normalize(vmin=0, vmax=1, clip=False)
    im = plt.imshow(np.zeros((28,28)), cmap='Greys', norm=norm)
    plt.grid(None)
    plt.title("Label: {}\nBatch: {}".format(c, 0))

    def update(i):
        im.set_array(figs[i])
        im.axes.set_title("Label: {}\nBatch: {}".format(c, periods[i]*batches_per_period))
        im.axes.get_xaxis().set_visible(False)
        im.axes.get_yaxis().set_visible(False)
        return im
    
    anim = FuncAnimation(fig, update, frames=range(len(figs)), interval=100)
    anim.save(fname, dpi=80, writer='ffmpeg')

for label in range(num_classes):
    make_2d_figs_gif(figs[label], periods, label, "./figs6/manifold_{}.mp4".format(label), plt.figure(figsize=(10,10)), batches_per_period)


Так как у нас снова модель на основе автоэнкодера, мы можем применять перенос стиля:

Код
# Трансфер стиля
def style_transfer(X, lbl_in, lbl_out):
    rows = X.shape[0]
    if isinstance(lbl_in, int):
        label = lbl_in
        lbl_in = np.zeros((rows, 10))
        lbl_in[:, label] = 1
    if isinstance(lbl_out, int):
        label = lbl_out
        lbl_out = np.zeros((rows, 10))
        lbl_out[:, label] = 1
    # Кодирем стиль входящего изображения
    zp = sess.run(z_mean, feed_dict={img:X, lbl:lbl_in, K.learning_phase():0})
    # Восстанавливаем из этого стиля, заменяя лейбл
    created = sess.run(decoded_z, feed_dict={z:zp, lbl:lbl_out, K.learning_phase():0})
    return created


# Картинка трансфера стиля
def draw_random_style_transfer(label):
    n = 10
    generated = []
    idxs = np.random.permutation(y_test.shape[0])
    x_test_permut = x_test[idxs]
    y_test_permut = y_test[idxs]
    prot = x_test_permut[y_test_permut == label][:batch_size]

    for i in range(num_classes):
        generated.append(style_transfer(prot, label, i)[:n])

    generated[label] = prot

    plot_digits(*generated, invert_colors=True)

draw_random_style_transfer(7)


Результаты


Сравнение с простым CVAE


Сверху оригиналы цифр, снизу восстановленные.

CVAE, скрытая размерность — 2


CVAE+GAN, скрытая размерность — 2


CVAE+GAN, скрытая размерность — 8


Сгенерированные цифры каждого лейбла сэмплированные из N(0|I):



Процесс обучения


Гифки


Трансфер стиля


За основу брались «7», из стиля которых создавались уже остальные цифры (здесь \dim Z = 8).

Вот так было с простым CVAE:


А вот так стало:


Заключение


На мой взгляд, получилось очень неплохо. Пройдя путь от простейших автоэнкодеров, мы дошли до генеративных моделей, а именно до VAE, GAN, поняли, что такое conditional модели, и почему важна метрика.
Мы также научились пользоваться keras’ом и совмещать его с голым tensorflow.

Всем спасибо за внимание, надеюсь было интересно!

Репозиторий со всеми ноутбуками

Полезные ссылки и литература


Оригинальная статья:
[1] Autoencoding beyond pixels using a learned similarity metric, Larsen et al, 2016, https://arxiv.org/abs/1512.09300

Туториал по VAE:
[2] Tutorial on Variational Autoencoders, Carl Doersch, 2016, https://arxiv.org/abs/1606.05908

Туториал по использованию keras вместе с tensorflow:
[3] https://blog.keras.io/keras-as-a-simplified-interface-to-tensorflow-tutorial.html
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332074/


Метки:  

[Из песочницы] Работа с гетерогенными контейнерами с C++17

Суббота, 01 Июля 2017 г. 20:25 + в цитатник
Привет, Хабр! В последнее время много говорят о C++17, особенно с появлением в России национальной рабочей группы по стандартизации. На просторах сети без особых проблем можно найти короткие примеры использования последнего стандарта C++. Всё бы хорошо, но по настоящему обширного перехода на новые стандарты не наблюдается. Поэтому можем наблюдать картину, в которогй любая библиотека, требующая минимум 14 стандарта уже считается modern постфактум.

В данной публикации разработаем небольшую библиотеку (3 функции (apply, filter, reduce) и одна как «домашнее задание» (map) :)) по удобной работе с гетерогенными контейнерами в рантайме (гетерогенность за счёт std::variant из 17 стандарта).

Из нового, помимо новых библиотечных типов, попробуем на вкус fold expressions и совсем немного structured binding

Введение


Для начала небольшое введение в тему гетерогенных контейнеров. Как известно, настоящих гетерогенных контейнеров, работающих в рантайме на c++ нет. В нашем распоряжении есть std::tuple, следы которого практически полностью исчезают в рантайме (not pay for what you don't use) и… впрочем всё. Всё остальное — лишь строительные блоки для построения собственных велосипедов библиотек.

Стоительных блока, которые позволяют нам сделать гетерогенный контейнер два — std::any и std::variant. Первый не помнит тип, поэтому его использование сильно ограничено. std::variant помнит тип и умеет матчить функторы на текущий тип с помощью std::visit (реализовано с помощью генерации таблицы методов и последующих переходов по ней). Реализация поистине магическая, а магия — единственное, что поможет сделать то, что сделать на первый взгляд невозможно :) (конечно возможно, ведь на c++ возможно всё). Внутри std::variant содержит не так много оверхеда, перенося boost версию в стандарт разработчики позаботились о производительности (относительной того, что было). Резюмируя, берём std::variant в качестве контейнера типов и базовой единицы гетерогенного контейнера.

Дисклеймер


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

Так же не претендую на уникальность, наверняка есть подобные хорошие библиотеки :)

Начало


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

struct Circle
{
    void Print() { cout << "Circle. " << "Radius: " << radius << endl; }
    double Area() { return 3.14 * radius * radius; }

    double radius;
};

struct Square
{
    void Print() { cout << "Square. Side: " << side << endl; }
    double Area() { return side * side * side * side; }

    double side;
};

struct EquilateralTriangle
{
    void Print() { cout << "EquilateralTriangle. Side: " << side << endl; }
    double Area() { return (sqrt(3) / 4) * (side * side); }

    double side;
};

using Shape = variant;

Так же для сравнения будем держать в уме её простой полиморфный аналог:

struct Shape
{
    virtual void Print() = 0;
    virtual double Area() = 0;
    virtual ~Shape() {};
};

struct Circle : Shape
{
    Circle(double val) : radius(val) {}

    void Print() override { cout << "Circle. " << "Radius: " << radius << endl; }
    double Area() override { return 3.14 * radius * radius; }

    double radius;
};

struct Square : Shape
{
    Square(double val) : side(val) {}

    void Print() override { cout << "Square. Side: " << side << endl; }
    double Area() override { return side * side * side * side; }

    double side;
};

struct EquilateralTriangle : Shape
{
    EquilateralTriangle(double val) : side(val) {}

    void Print() override { cout << "EquilateralTriangle. Side: " << side << endl; }
    double Area() override { return (sqrt(3) / 4) * (side * side); }

    double side;
};

Создадим вектор и попытаемся стандартными средствами добиться полиморфного поведения. Проитерируемся по вектору и вызовем функцию Print.

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

vector shapes;
shapes.emplace_back(new Square(8.2));
shapes.emplace_back(new Circle(3.1));
shapes.emplace_back(new Square(1.8));
shapes.emplace_back(new EquilateralTriangle(10.4));
shapes.emplace_back(new Circle(5.7));
shapes.emplace_back(new Square(2.9));

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

vector> shapes;
shapes.emplace_back(make_shared(8.2));
shapes.emplace_back(make_shared(3.1));
shapes.emplace_back(make_shared(1.8));
shapes.emplace_back(make_shared(10.4));
shapes.emplace_back(make_shared(5.7));
shapes.emplace_back(make_shared(2.9));

Теперь выглядит лучше. Однако для новичка понятности в коде явно не прибавилось. Но не будем разводить холивар, выполним нашу задачу:

for (shared_ptr shape: shapes)
{
    shape->Print();
}

// Вывод:
// Square. Side: 8.2
// Circle. Radius: 3.1
// Square. Side: 1.8
// EquilateralTriangle. Side: 10.4
// Circle. Radius: 5.7
// Square. Side: 2.9

Так же попробуем реализовать схожее поведение для гетерогенного контейнера:

vector operations;
operations.emplace_back(EquilateralTriangle { 5.6 });
operations.emplace_back(Square { 8.2 });
operations.emplace_back(Circle { 3.1 });
operations.emplace_back(Square { 1.8 });
operations.emplace_back(EquilateralTriangle { 10.4 });
operations.emplace_back(Circle { 5.7 });
operations.emplace_back(Square { 2.9 });

Здесь уже никаких указателей. Без проблем можно работать с объектами на стеке. Так же вместо коструктора можно использовать aggregate initialization для в «меру простых» типов.

Однако просто проитерироваться и вызвать функцию уже не удастся. Попробуем сделать это средствами, которые предоставляет std::variant. Для этого имеем функцию std::visit, так же нужно создать класс функторов.

Всё будет выглядеть подобным образом:

struct Visitor
{
    void operator()(Circle& c) { c.Print(); }
    void operator()(Square& c) { c.Print(); }
    void operator()(EquilateralTriangle& c) { c.Print(); }
};

...
...
...

for (Shape& shape: shapes)
{
    visit(Visitor{}, shape);
}

Вывод аналогичный. Так же такое же поведение можно проэмулировать с помощью constexpr if. Здесь уже кому что больше нравится.

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

Реализуем самые частые и всеобъемлющие функции: apply, filter, reduce.

Шаг 1


Для начала упростим себе задачу. Первый шаг достаточно притивен — описывался уже не раз.
Возьмём variadic templates, механизм наследования и знание о том, что лямбда-функции разворачиваются в обычные стуктуры — функторы. Унаследуемся от набора лябмд и создадим функцию, которая поможет нам вывести шаблонные типы:

template < typename... Func >
class Visitor : Func... { using Func::operator()...; }
template < class... Func > make_visitor(Func...) -> Visitor < Func... >;

Теперь вместо создания классов с функторами мы можем пользоваться набором лямбд, которые будут матчится по своим сигнатурам:

for (Shape& shape: shapes)
{
    visit(make_visitor(
                        []](Circle& c) { c.Print(); },
                        []](Square& c) { c.Print(); },
                        []](EquilateralTriangle& c) { c.Print(); }
                      ), shape);
}

Также можем воспользоваться выводом типов с generic параметром:

for (Shape& shape: shapes)
{
    visit(make_visitor([]](auto& c) { c.Print(); }), shape);
}

Получилось достаточно симпатично и в меру коротко.

Apply


Осталось собрать всё вместе и получить функцию apply для гетерогенных последовательностей:

template <
           typename InputIter,
           typename InputSentinelIter,
           typename... Callable
         >
void apply(InputIter beg,
           InputSentinelIter end,
           Callable... funcs)
{
    for (auto _it = beg; _it != end; ++_it)
        visit(make_visitor(funcs...), *_it);
};

Готово. Показанная техника на новизну не претендует, любой разработчик, так или иначе работавший с boost::variant давно реализовал для себя нечто подобное http://en.cppreference.com/w/cpp/utility/variant/visit, https://habrahabr.ru/post/270689/).

Теперь мы можем использовать функцию подобным образом:

apply(shapes.begin(), shapes.end(), [](auto& shape) { shape.Print(); });

или

apply(shapes.begin(), shapes.end(), 
      [] (Circle& shape)              { shape.Print(); },
      [] (Square& shape)              { shape.Print(); },
      [] (EquilateralTriangle& shape) { shape.Print(); });

Как видите, получилось довольно непохо. Однако, если мы передадим функторы не для всех типов, которые есть в std::variant, получится ошибка компиляции. Чтобы избежать этого, по подобию SFINAE сделаем функтор с elipsis, который будет вызываться при отсутствии любой другой альтернативы, причём в порядке вызова он будет самым последним вариантом.

template <
           typename InputIter,
           typename InputSentinelIter,
           typename... Callable
         >
void apply(InputIter beg,
           InputSentinelIter end,
           Callable... funcs)
{
    for (auto _it = beg; _it != end; ++_it)
        visit(make_visitor(funcs..., [](...){}), *_it);
};

Теперь можем передавать функторы не для всех типов, для отсутствующих будет произведён вызов пустой лямбды:

// Выводит информацию только для типов Circle
apply(shapes.begin(), shapes.end(), [] (Circle& shape) { shape.Print(); }); 

Для наглядного примера просто покажу, как сделать подобное с помощью динамического полиморфизма:

// Выводит информацию только для типов Circle
for_each(shapes.begin(), shapes.end(),
         [] (shared_ptr shape) {
             if (dynamic_pointer_cast(shape))
                    shape->Print();
         });

Далеко не самый приятный вид.

Filter


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

template <
           typename InputIter,
           typename InputSentinelIter,
           typename OutputIter,
           typename... Callable
         >
void filter(InputIter beg,
            InputSentinelIter end,
            OutputIter out,
            Callable... funcs)
{
    for (auto _it = beg; _it != end; ++_it)
    {
        if (visit(make_visitor(funcs..., [] (...) { return false; }), 
                  *_it))
            *out++ = *_it;
    }
};

Пользоваться реализованной функцией можно следующим образом:

vector filtered;
filter(shapes.begin(), shapes.end(),
       back_inserter(filtered),
       [] (Circle& c) { return c.radius > 4.; },
       [] (Square& s) { return s.side < 5.; });

apply(filtered.begin(), filtered.end(), [](auto& shape) { shape.Print(); });

// Вывод:
// Square. Side: 1.8
// Circle. Radius: 5.7
// Square. Side: 2.9

Аналог, реализованный с помощью динамического полиморфизма:

vector> filtered;
copy_if(shapes.begin(), shapes.end(),
        back_inserter(filtered),
        [] (shared_ptr shape)
        {
            if (auto circle = dynamic_pointer_cast(shape))
            {
                return circle->radius > 4.;
            }
            else if (auto square = dynamic_pointer_cast(shape))
            {
                return square->side < 5.;
            }
            else return false;
        });

for_each(filtered.begin(), filtered.end(), [](shared_ptr shape) { shape->Print(); });

// Вывод:
// Square. Side: 1.8
// Circle. Radius: 5.7
// Square. Side: 2.9

Reduce


Осталось реализовать reduce (аналог std::accumulate) и map (аналог std::transform). Реализация этих функций несколько сложнее, чем это было с apply и filter. Для reduce мы используем функторы с двумя параметрами (значение аккумулятора и сам объект). Для того, чтобы реализовать схожее поведение, можно частично применить лямбда функции таким образом, чтобы для std::variant остались функции одного аргумента. Красивого решения для c++ по частичному применению нет, быстрый способ — захват необходимого контекста с помощью другой лямбды. Учитывая, что мы работаем не с одной лямбдой, а с variadic pack, код раздувается и начинает быть плохо читаемым. Спасает нас обработка вариадиков с помощью fold expressions. Ветераны знают, какими костылями приходилось раньше сворачивать списки типов.

template <
           typename InputIter,
           typename InputSentinelIter,
           typename AccType,
           typename... Callable
         >
struct reduce < InputIter, InputSentinelIter, AccType, false, Callable... >
{
    constexpr auto operator()(InputIter beg, InputSentinelIter end,
                              AccType initial_acc, Callable... funcs)
    {
        for (auto _it = beg; _it != end; ++_it)
        {
            initial_acc = visit(utility::make_overloaded_from_tup(
                tup_funcs(initial_acc, funcs...),
                make_index_sequence{},
                [&initial_acc] (...) { return initial_acc; } ),
                                *_it);
        }
        return initial_acc;
    }
};

Для того, чтобы сделать что то подобное, было решено воспользоваться старым добрым кортежем (std::tuple). Обработка его элементов не слишком сложная, в любой момент можно написать свою. И так, я создаю кортеж из лямбд, который трасформируется в новый кортеж путём оборачивания каждой лямбды в другую с захватом значения аккумулятора. Благо трансформация кортежа, используя новый стандарт, пишется относительно легко:

template < typename... Types, typename Func, size_t... I >
constexpr auto tuple_transform_impl(tuple t, Func func, index_sequence)
{
    return make_tuple(func(get(t)...));
}

template < typename... Types, typename Func >
constexpr auto tuple_transform(tuple t, Func f)
{
    return tuple_transform_impl(t, f make_index_sequence{});
}

Для того, чтобы создать объемлющую лябду, мне нужно знать тип второго аргумента приходящей лямбды. С помощью helper'ов, найденных на просторах интернета, можно скастовать лямбду к структуре, имеющей оператор вызова и путём матчинга получить желаемый тип.

Выглядит это всё примерно так:

template < typename Func, typename Ret, typename _, typename A, typename... Rest >
A _sec_arg_hlpr(Ret (Func::*)(_, A, Rest...));

template < typename Func >
using second_argument = decltype(_sec_arg_hlpr(&Func::operator()));

template < typename AccType, typename... Callable >
constexpr auto tup_funcs(AccType initial_acc, Callable... funcs)
{
    return tuple_transform(tuple{ funcs... },
        [&initial_acc](auto func) {
            return [&initial_acc, &func] (second_argument arg) {
                return func(initial_acc, arg); };
        });
}

Всё бы хорошо, но эти чудеса не работают с generic функциями, типы входных аргументов которых мы не можем получить по определению. Поэтому, используя tag dispatching и создав простенький трейт для проверки функции мы создаём для этого случая свою реализацию.
Резюмируя, получаем для reduce следующие возможности для использования:

using ShapeCountT = tuple;
auto result = reduce(shapes.begin(), shapes.end(),
                     ShapeCountT{},
                     [] (ShapeCountT acc, Circle& item)
                     {
                         auto [cir, sq, tr] = acc;
                         return make_tuple(++cir, sq, tr);
                     },
                     [] (ShapeCountT acc, Square& item)
                     {
                         auto [cir, sq, tr] = acc;
                         return make_tuple(cir, ++sq, tr);
                     },
                     [] (ShapeCountT acc, EquilateralTriangle& item)
                     {
                         auto [cir, sq, tr] = acc;
                         return make_tuple(cir, sq, ++tr);
                     });
auto [cir, sq, tr] = result;
cout << "Circle count: " << cir
     << "\tSquare count: " << sq
     << "\tTriangle count: " << tr << endl;

// Вывод:
// Circle count: 2 Square count: 3 Triangle count: 2

Функция map реализуется на базе похожих идей, описание её реализации и саму реализацию опущу. Для тренировки своих meta скиллов предлагаю реализовать её самим :)

Что дальше?


Немного об ошибках. Сделаем шаг в сторону и увидим подобное сообщение:

Скрин ошибки


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

Есть несколько подходов, как можно элегантно или не очень сказать об истинной природе ошибки.
В следующей раз разбавим написанное с Concepts TS из gcc-7.1.

Резюмируя, можно сказать, что подобный подход может сильно пригодиться для работы с библиотеками, которым приходилось использовать TypeErasure технику, для шаблонных классов с разной специализацией, для примитивной эмуляции полиморфизма,…

А как бы дополнили / использовали этот функционал вы? Пишите в комментариях, будет интересно почитать

Вышеприведённый код доступен тут.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332084/


Метки:  

[Из песочницы] Интеграция 1С с DLL с помощью Python

Суббота, 01 Июля 2017 г. 17:33 + в цитатник
Привет Хабр! Недавно я разработал алгоритм для логистики, и нужно было его куда-то пристроить. Помимо веб-сервиса решено было внедрить данный модуль в 1С, и тут появилось довольно много подводных камней.

Начнем с того, что сам алгоритм представлен в виде dll библиотеки, у которой одна точка входа, принимающая JSON строку как параметр, и отдающая 2 колбэка. Первый для отображения статуса выполнения, другой для получения результата. С web-сервисом все довольно просто, у питона есть замечательный пакет ctypes, достаточно подгрузить нужную библиотеку и указать точку входа.

Выглядит это примерно так:

import ctypes
def callback_recv(*args):
	print(args)

lib = ctypes.cdll.LoadLibrary('test.dll')
Callback = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
my_func = getattr(lib, '_ZN7GtTools4testEPKcPFviS1_E')
cb_func = Callback(callback_recv)
my_func(ctypes.c_char_p('some data'), cb_func)
 

Как можно заметить, точка входа не совсем читабельная. Чтобы найти данную строчку в скомпилировнанных данных, нужно открыть соответствующий файл с расширением .lib и применить утилиту objdump с параметром -D, в выводе легко можно найти нужный метод по названию.

Данное коверканье метода происходит из-за того, что компилятор манглит («mangle» — калечить) название всех точек входа, причем разные компиляторы «калечат» по разному. В примере указан метод полученный MinGW

В 1С все оказалось гораздо менее тривиально. Для подключения dll нужно, чтобы у нее был специальный интерфейс Native API, позволяющий зарегестрировать Внешнюю Компоненту. Все написал по примеру, но ничего не взлетало. Я подумал, что это из-за gcc. Все мои попытки поставить Visual Studio были провальны, то ничего не устанавливалось, то не хватало стандартных библиотек.

Уже засыпая мне в голову пришла гениальная гипотеза. Наверное данную проблему не могли не оставить питонисты, ведь на Питон разработно все, что вообще возможно. А-ля правило интернета 34, только по отношению к чудесному Python. И ведь я оказался прав!

Для python существует пакет win32com который позволяет регестрировать Python объекты, как COM объекты. Для меня это было какой то магией, ведь я даже не очень понимаю что такое COM объект, но знаю что он умеет в 1С.

Пакет pypiwin32 не нужно ставить с помощью pip, а скачать его установщик, т.к. почему-то объекты не регестрировались после установки pip'ом.

Разобравшись с небольшим примером, я взялся за разработку. Для начала нужно создать Объект с интерфейсом идентифицирующим COM-Объект в системе

class GtAlgoWrapper():
    # com spec
    _public_methods_ = ['solve','resultCallback', 'progressCallback',] # методы объекта
    _public_attrs_ = ['version',] # атрибуты объекта
    _readonly_attr_ = []
    _reg_clsid_ = '{2234314F-F3F1-2341-5BA9-5FD1E58F1526}' # uuid объекта
    _reg_progid_= 'GtAlgoWrapper' # id объекта
    _reg_desc_  = 'COM Wrapper For GTAlgo' # описание объекта
    def __init__(self):
        self.version = '0.0.1'
        self.progressOuterCb = None
        # ...

    def solve(self, data):
        # ...
        return ''

    def resultCallback(self, obj): 
        # ...
        return obj

    def progressCallback(self, obj): 
       # в колбэк необходимо передавать 1С объект, в котором идет подключение 
       # например ЭтотОбъект или ЭтаФорма
        if str(type(obj)) == "": 
            com_obj = win32com.client.Dispatch(obj)
            try:
               # сохраним функцию из 1С (progressCallback) в отдельную переменную
               self.progressOuterCb = com_obj.progressCallback1C; 
           except AttributeError:
                raise Exception('"progressCallback" не найден в переданном объекте')
        return obj

и конечно опишем его регистрацию

def main():
    import win32com.server.register
    win32com.server.register.UseCommandLine(GtAlgoWrapper)
    print('registred')

if __name__ == '__main__':
    main()

Теперь при запуске данного скрипта в системе появится объект GtAlgoWrapper. Его вызов из 1С будет выглядеть вот так:

Функция progressCallback1C(знач, тип) Экспорт
    Сообщить("значение = " + знач);
    Сообщить("тип = " + тип);
КонецФункции
//...
Процедура Кнопка1Нажатие(Элемент)
    //Создадим объект
   ГТАлго =  Новый COMОбъект("GtAlgoWrapper");
    //Установим колбэки
   ГТАлго.progressCalback(ЭтотОбъект);
   //...
   Данные = ...; // JSON строка
   ГТАлго.solve(Данные);
КонецПроцедуры

Таким образом, все попадающие в колбэки даные можно будет обработать. Единственное, что может еще остаться непонятным — как передать данные из dll в 1C:

_dependencies = ['libwinpthread-1.dll',
                     'libgcc_s_dw2-1.dll',
                     # ...,
                     'GtRouting0-0-1.dll']
def solve(self, data):
        prefix_path = 'C:/release'
        # должны быть подключены все зависимые библиотеки
        try:
            for dep in self._dependencies:
                ctypes.cdll.LoadLibrary(os.path.join(prefix_path, dep))
            # запоминаем библиотеку с нужной нам точкой входа
            lib = ctypes.cdll.LoadLibrary(os.path.join(prefix_path, 'GtAlgo0-0-1.dll'))
        except WindowsError:
            raise Exception('cant load' + dep)

        solve_func = getattr(lib, '_ZN6GtAlgo5solveEPKcPFviS1_ES3_')
       
        # создаем колбэки
        StatusCallback = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
        ResultCallback = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
        scb_func = StatusCallback(self.progressOuterCb)
        rcb_func = ResultCallback(self.resultOuterCb)
        # колбэки 1C превратились в функции которые мы передадим в DLL. Magic!
        if self.resultOuterCb is None:
            raise Exception('resultCallback function is not Set')
        if self.progressOuterCb is None:
            raise Exception('progressCallback function is not Set')
        # запустим алгоритм
        solve_func(ctypes.c_char_p(data), scb_func, rcb_func)

Для успешной работы, в первую очередь требуется вызов python-скрипта, чтобы зарегистрировать класс GtAlgoWrapper, а затем уже можно смело запускать конфигурацию 1С.

Вот так просто можно связать dll библиотеку и 1C с помощью питона, не уползая в сильные дебри.
Всем Магии!

Полезные ссылки
docs.python.org/3/library/ctypes.html — Пакет ctypes
citforum.ru/book/cook/dll0.shtml — Динамические библиотеки для чайников
habrahabr.ru/post/191014 — NativeAPI
infostart.ru/public/115486 — COM объект на C++
infostart.ru/public/190166 — COM объект на Python
pastebin.com/EFLnnrfp — Полный код скрипта на Python из статьи
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332082/


Метки:  

«Ты, гроза, грозись, а мы друг за друга держись!» — сказ о том, как я ADSL-модем спасал

Суббота, 01 Июля 2017 г. 16:04 + в цитатник
«Модем горит,
Провайдер плачет.
Но он не смог поступить иначе.
Гремит гроза...»


Гроза! Стихия! Караул! Иной раз тяжело предугадать, какой ущерб она может учинить домашней электронике… Благо, её можно предупредить, тем самым исключив возможность выхода из строя, скажем, хотя бы домашних маршрутизаторов и модемов — обычно они в первую очередь и подвергаются воздействию буйных действ природы.


За прошедшие лета я похоронил ровно два модема — причиной их гибели послужили резкие перепады в сети электропитания. В этом же году я наконец набрался смелости порвать сей порочный круг и попытаться спасти бедный D-Link от преждевременной погибели!

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

То-то и оно! Потому мне и подумалось, что будет неплохо соорудить некое подобие EAS (Emergency Alert System) для дома, с пищанием, оповещением по TV и прочими финтифлюшками.

Для начала я набросал две простеньких схемки, по которым можно было бы воплотить в жизнь задуманное. Первая из них – как д'oлжно получать текущий код погоды EAS-клиенту; вторая же – что должно быть отображено на экране телевизора.

Первая

и
Вторая


Но, как говорится в одной небезызвестной поговорке, «скоро сказка сказывается, да не скоро дело делается». Думу долгую думал я, как же выводить буду на телевизор оповещение; то и дело откладывал на потом реализацию проекта, но вот «потом» и пришло нежданно-негаданно – в голове спонтанно возникла донельзя простая идея того, как же всё-таки разделаться с этой проблемой.

Решением оказалось превращение Raspberry Pi B+ в EAS-клиент путём запуска браузера epiphany в kiosk-режиме. Браузер цепляется за локальный веб-сервер, который, в свою очередь, возвращает следующий код:

...



...



...


Таким образом, благодаря простецкому js-скрипту и тегу iframe, мы создали костыль и лишние проблемы облегчили себе жизнь; ведь ежели домашний сервер окажется недоступным вследствие того, что его отключили перед грозой, браузер не выдаст стандартную ошибку, мол, «увы и ах, доступа к сети больше нет» (и будет долго-долго ждать, пока кто-нибудь мышкой ткнёт в кнопку «обновить страницу»), а просто продолжит ежеминутно обновлять страницу и пытаться подгрузить содержимое iframe.

Но не будем забегать так далеко вперёд, давайте же разберёмся, как устроен EAS-клиент изнутри; если быть более точным, как происходит загрузка:

  1. Старт ОС (raspbian)
  2. Обращение к /etc/rc.local
  3. Инициализация иксов через xinit
  4. Запуск браузера
  5. Подключение к серверу
  6. Получение данных

Файл /etc/rc.local отличается от оригинального одной-единственной строкой:

sudo xinit ./home/eas/eas.boot & ## Инициируем загрузку иксов

А файл /home/eas/eas.boot ответственен за запуск браузера:

#!/bin/sh
xset -dpms
xset s off
xset s noblank
matchbox-window-manager -use_titlebar no & ## Запускаем оконный менеджер
unclutter -idle 0.01 -root & ## Скрываем курсор мыши
WEBKIT_DISABLE_TBS=1 epiphany-browser -a http://podivilov.local/ --profile /home/eas/.config ## Запускаем браузер в kiosk-режиме на весь экран

Наш браузер обращается к домашнему серверу, обозначенному как podivilov.local; тот же, в свою очередь, запрашивает у внешнего сервера данные о погоде, default и extra коды (default — целое число, нуль или единица; нуль — EAS не активирована, единица — активирована; extra — код погоды, полученный с сайта прогноза погоды, требуемый для отладки).

Происходит это следующим образом:

$code_default = file_get_contents('https://***.podivilov.ru/api/method/weather.getCode/?token=********************');
$code_extra = file_get_contents('https://***.podivilov.ru/api/method/weather.getCode.extra/?token=********************');

Данные о погоде внешний сервер получает с сайта openweathermap.org:

$json = file_get_contents('http://api.openweathermap.org/data/2.5/weather?lat=**.******&lon=**.******&APPID=********************************'); // lat & lon - широта и долгота, а APPID - api-код, который можно получить опосля регистрации на сайте
$data = json_decode($json,true);
$result = substr($data['weather'][0]['icon'], 0, -1);

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

В зависимости от условий погоды и полученного кода наша система выводит на экран различные наборы цифр: например, если ожидается сильная гроза и следует выключить пробки №1 (маршрутизатор/модем) и №2 (прочая бытовая техника), EAS-система отображает код 12 (пробка №1 и №2).

Пробки пронумерованы


Помимо этого, каждые полчаса на домашних компьютерах менеджер задач запускает следующий скрипт (оный опрашивает сервер, нет ожидается ли гроза; если ответ положителен, запускается программа, предупреждающая о возможном наступлении катаклизма):

dim xHttp: Set xHttp = createobject("MSXML2.ServerXMLHTTP")

xHttp.Open "GET", "https://***.podivilov.ru/api/method/weather.getCode/?token=********************", False
xHttp.setOption 2, 13056
xHttp.Send

If xHttp.responseText = "1" Then

	Set objShell = CreateObject("WScript.Shell") 
	objShell.Run """C:\путь_к_программе\EAS.exe""" 
	Set objShell = Nothing

Else

	WScript.Quit

End If

Но лучше один раз увидеть, чем тысячу раз услышать. Собственно, пред вами результат того, о чём я повествовал выше:




Вот и сказочке конец, а кто слушал – молодец… Берегите себя и свои роутеры!

Спасибо за внимание!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332072/


Метки:  

Хакеры и биржи: как атакуют сферу финансов

Суббота, 01 Июля 2017 г. 15:29 + в цитатник


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

Сегодня мы рассмотрим несколько примеров реальных атак на банки и биржи и поговорим о последствиях этих киберинцидентов.

Популярная цель хакеров


Нападения хакеров на сферу финансов — дело столь же привычное, как и утренний кофе для большинства людей. На клиентов «Сбербанка» совершается около 5 тыс. атак в неделю. Не лучше ситуация и с целевыми взломами — в 2016 году хакеры попытались украсть у российских банков 2,87 млрд рублей, сообщает Центробанк.

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

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

Кража денег из банков с помощью уязвимости в системе переводов


В прошлом году система международных денежных переводов SWIFT неоднократно подвергалась хакерским атакам. Воспользовавшись уязвимостями в этой системе, хакеры сумели вывести $81 млн из Центробанка Бангладеш. Ещё $9 млн преступники похитили у банка в Эквадоре. Летом 2016 года $10 млн было украдено у неназванного украинского банка. Во всех этих случаях хакеры действовал одинаково: внедрялись в банки, подключенные к SWIFT, а после получали данные операторов, имеющих право на создание и одобрение SWIFT-сообщений, и проводили поддельные транзакции.



Эксперты предполагают, что атаки на систему переводов — дело хакерской группировки Lazarus. Интересно, что представители SWIFT поначалу заявляли, что уязвимость не является причиной краж. Но после нескольких инцидентов компания взялась за усиление безопасности.

В феврале 2016 года со счета российского Металлинвестбанка исчезло 667 млн рублей. Атака пришлась на АРМ КБР (автоматизированное рабочее место клиента Банка России), с которого ведётся управление счетом в Центробанке. В какой-то момент представители банка заметили, что с устройства отправляются несанкционированные переводы на счета частных лиц в банках по всей стране. По словам специалистов, за инцидентом в Металлинвестбанке и ещё как минимум 13 взломами стоит группировка Buhtrap, члены которой были задержаны в июне 2016 года. Хакеры запускали троян в банковскую сеть, рассылая письма от имени Центробанка, собирали логины и пароли от доменных учетных записей, а после получали доступ к АРМ КБР и подменяли платежные документы.

Похищение торговых алгоритмов и сбои в биржевой торговле


В июле 2015 года торги на Нью-Йоркской фондовой бирже (NYSE) были приостановлены на несколько часов. Официально причиной сбоя назвали внутренние неполадки, но журналистов и некоторых экспертов такая версия не убедила. По их мнению, виной всему стали хакерские атаки. Вину за случившееся приписывали как группе Anonymous, так и китайским киберпреступникам. К слову, Anonymous пыталась атаковать биржу и в 2011 году, но тогда к серьёзным последствиям это не привело. О том, каким способом была взломана биржа в 2015 году (если атака действительно имела место), доподлинно неизвестно.



Изображение: Christine Puccio, CC BY-SA 2.0

На Московской бирже в том же 2015 году произошла не менее загадочная ситуация. В начале февраля во время торгов курс рубля снизился на 15%, поскольку один из трейдеров — казанский Энергобанк — продавал валюту по нерыночным ценам. За 15 минут такой торговли игрок потерял 244 млн рублей. В случившемся банк обвинил хакеров. За расследование инцидента взялись специалисты из Group-IB, которые установили, что банк и впрямь пострадал от злоумышленников. Механизм атаки оказался простым: хакеры заразили трояном Corcow трейдинговую систему банка, получив тем самым удаленный контроль над ней. Однако многие, в том числе и первый зампредседателя Центробанка Сергей Швецов, посчитали, что дело не в хакера, а в том, что банк сознательно манипулировал валютой.

Американская биржа Nasdaq подверглась крупной хакерской атаке. В 2010 году ФБР заметило попытку проникновения на центральные сервера биржи. В результате расследования, о ходе которого докладывали самому президенту США, было установлено, что в систему проникли, используя несколько ранее не обнаруженных в системе уязвимостей. Такой подход, по сообщению зарубежных журналистов, характерен для спецслужб. Однако в дальнейшем выяснилось, что в Nasdaq «наследили» несколько независимых друг от друга группировок. Существуют разные предположения о цели атаки от банальной кражи денег до попытки уничтожить биржу. Представители Nasdaq заявили, что преступники охотились за инсайдерской информацией сервиса Directors Desk, который содержит данные 300 компаний.

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

Кража инсайдерской информации


Похищения данных, способных повлиять на ход торгов, случаются на биржах куда чаще попыток помешать работе и украсть торговые алгоритмы. Такую информацию куда проще использовать или продать. Но в этом случае атакам подвергаются не только сами биржи, но и другие влиятельные в финансовом мире компании. Показательный случай — кража инсайдерской информации у Dow Jones&Co.

О взломе и хищении данных 3500 клиентов компания сообщила в 2015 году. Но оказалось, что этот инцидент с Dow Jones не самый интересный. На тот момент ФБР уже расследовало кражу неопубликованных статей и другой информации, дающей преимущество в ходе торгов. Одна из служб компании — Factiva — ещё до официальной публикации собирает важные финансовые данные из более чем 4000 источников, а потому ее взлом особенно интересен хакерам.

Аналогичная проблема возникла и у американских ресурсов для публикации пресс-релизов компаний PRNewswire, Marketwired и Businesswire. Они, сами того не замечая, целых пять лет делились с хакерами важной для рынка информацией до ее публикации. Доступ к данным киберпреступники получили с помощью фишинговых атак. Хакеры работали в связке с трейдерами. Последние использовали полученные данные для торгов на бирже, а вырученные средства переводили в офшоры. Ущерб от действий группы оценивается по разным данным в сумму до 30 до 100 млн долларов.

Заключение


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

Финансовые компании разрабатывают различные средства защиты и самостоятельно — прием они могут быть направлены не только на борьбу с последствиями взломов, но и обычных ошибок ИТ-систем. К примеру, ошибки в работе биржевых систем могут приводить в том числе и к некорректному отображению торговых данных или неверному расчету гарантийного обеспечения для удержания позиции (ошибка может привести даже к преждевременному закрытию сделки)

Для того, чтобы минимизировать возможный ущерб брокерские компании разрабатывают различные системы защиты клиентов. О том, как реализована подобная защита в торговой системе ITinvest MatriX можно прочитать по ссылке.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332080/


Метки:  

[Перевод] Используем IoC-контейнер Laravel на полную мощность

Суббота, 01 Июля 2017 г. 14:52 + в цитатник

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


Примеры в данной статье основаны на Laravel 5.4.26, другие версии могут отличаться.


Введение в Dependency Injection


Я не буду объяснять, что такое DI и IoC в этой статье — если вы не знакомы с этими принципами, вы можете прочитать статью "What is Dependency Injection?" от Fabien Potencier (создателя фреймворка Symfony).


Получение контейнера (Container)


В Laravel существует несколько способов получения сущности контейнера * и самый простой из них это вызов хелпера app():


$container = app();

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


* В Laravel есть класс Application, который наследуется от Container (именно поэтому хелпер называется app()), но в этой статье я буду описывать только методы класса Container.


Использование Illuminate\Container вне Laravel


Для использования контейнера Laravel вне фреймворка необходимо установить его с помощью Composer, после чего мы можем получить контейнер так:


use Illuminate\Container\Container;

$container = Container::getInstance();

Пример использования


Самый простой способ использования контейнера — указать в конструкторе классы, которые необходимы вашему классу используя type hinting:


class MyClass
{
    private $dependency;

    public function __construct(AnotherClass $dependency)
    {
        $this->dependency = $dependency;
    }
}

Затем, вместо создание объекта с помощью new MyClass, нужно вызвать метод контейнера make():


$instance = $container->make(MyClass::class);

Контейнер автоматически создаст и внедрит зависимости, что будет эквивалентно следующему коду:


$instance = new MyClass(new AnotherClass());

(За исключением того случая, когда у AnotherClass есть свои зависимости. В таком случае контейнер автоматически создаст и внедрит его зависимости, зависимости его зависимостей и т.д.)


Реальный пример


Ниже показан более реальный пример, который взят из документации PHP-DI. В нем логика отправки сообщения отделена от логики регистрации пользователя:


class Mailer
{
    public function mail($recipient, $content)
    {
        // Send an email to the recipient
        // ...
    }
}

class UserManager
{
    private $mailer;

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function register($email, $password)
    {
        // Create the user account
        // ...

        // Send the user an email to say hello!
        $this->mailer->mail($email, 'Hello and welcome!');
    }
}

use Illuminate\Container\Container;

$container = Container::getInstance();

$userManager = $container->make(UserManager::class);
$userManager->register('dave@davejamesmiller.com', 'MySuperSecurePassword!');

Связывание интерфейса и реализации


Для начала определим интерфейсы:


interface MyInterface { /* ... */ }
interface AnotherInterface { /* ... */ }

Затем создадим классы, реализующие эти интерфейсы. Они могут зависеть от других интерфейсов (или других классов, как это было ранее):


class MyClass implements MyInterface
{
    private $dependency;

    public function __construct(AnotherInterface $dependency)
    {
        $this->dependency = $dependency;
    }
}

Теперь свяжем интерфейсы с реализацией с помощью метода bind():


$container->bind(MyInterface::class, MyClass::class);
$container->bind(AnotherInterface::class, AnotherClass::class);

И передадим название интерфейса вместо названия класса в метод make():


$instance = $container->make(MyInterface::class);

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


Fatal error: Uncaught ReflectionException: Class MyInterface does not exist

Это происходит потому, что контейнер пытается создать экземпляр интерфейса (new MyInterface), который не является классом.


Реальный пример


Ниже представлен реальный пример связывания интерфейса с конкретной реализацией — изменяемый драйвер кеша:


interface Cache
{
    public function get($key);
    public function put($key, $value);
}

class RedisCache implements Cache
{
    public function get($key) { /* ... */ }
    public function put($key, $value) { /* ... */ }
}

class Worker
{
    private $cache;

    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }

    public function result()
    {
        // Use the cache for something...
        $result = $this->cache->get('worker');

        if ($result === null) {
            $result = do_something_slow();

            $this->cache->put('worker', $result);
        }

        return $result;
    }
}

use Illuminate\Container\Container;

$container = Container::getInstance();
$container->bind(Cache::class, RedisCache::class);

$result = $container->make(Worker::class)->result();

Связывание абстрактных и конкретных классов


Связывание может быть использовано и с абстрактным классом:


$container->bind(MyAbstract::class, MyConcreteClass::class);

Или для замены класса его потомком (классом, который наследуется от него):


$container->bind(MySQLDatabase::class, CustomMySQLDatabase::class);

Custom Bindings


Если объект при создании требует дополнительной настройки, вы можете передать замыкание вторым параметром в метод bind() вместо названия класса:


$container->bind(Database::class, function (Container $container) {
    return new MySQLDatabase(MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASS);
});

Каждый раз, когда будет запрашиваться класс Database, будет создан новый экземпляр MySQLDatabase с указанной конфигурацией (если нужно иметь только один экземпляр класса, используйте Singleton, о котором говорится ниже).


Замыкание получает в качестве первого параметра экземпляр класса Container, который может быть использован для создания других классов, если это необходимо:


$container->bind(Logger::class, function (Container $container) {
    $filesystem = $container->make(Filesystem::class);

    return new FileLogger($filesystem, 'logs/error.log');
});

Замыкание также можно использовать для настройки класса после создания:


$container->bind(GitHub\Client::class, function (Container $container) {
    $client = new GitHub\Client;
    $client->setEnterpriseUrl(GITHUB_HOST);

    return $client;
});

Resolving Callbacks


Вместо того, чтобы полностью перезаписывать биндинг, мы может использовать метод resolving() для регистрации коллбеков, которые будут вызваны после создания требуемого объекта:


$container->resolving(GitHub\Client::class, function ($client, Container $container) {
    $client->setEnterpriseUrl(GITHUB_HOST);
});

Если было зарегистрировано несколько коллбеков, все они будут вызваны. Это также работает для интерфейсов и абстрактных классов:


$container->resolving(Logger::class, function (Logger $logger) {
    $logger->setLevel('debug');
});

$container->resolving(FileLogger::class, function (FileLogger $logger) {
    $logger->setFilename('logs/debug.log');
});

$container->bind(Logger::class, FileLogger::class);

$logger = $container->make(Logger::class);

Также есть возможность регистрации коллбека, который будет вызываться при создании любого класса (это может быть полезно для логгирования или при отладке):


$container->resolving(function ($object, Container $container) {
    // ...
});

Расширение класса


Вы также можете использовать метод extend() для того, чтобы обернуть оригинальный класс и вернуть другой объект:


$container->extend(APIClient::class, function ($client, Container $container) {
    return new APIClientDecorator($client);
});

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


Singleton


Каждый раз, когда возникает необходимость в каком либо классе (если указано имя класса или биндинга, созданного с помощью метода bind()), создается новый экземпляр требуемого класса (или вызывается замыкание). Для того, чтобы иметь только один экземпляр класса необходимо вызвать метод singleton() вместо метода bind():


$container->singleton(Cache::class, RedisCache::class);

Пример с замыканием:


$container->singleton(Database::class, function (Container $container) {
    return new MySQLDatabase('localhost', 'testdb', 'user', 'pass');
});

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


$container->singleton(MySQLDatabase::class);

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


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


$container->instance(Container::class, $container);

Произвольное название биндинга


При биндинге вы можете использовать произвольную строку вместо названия класса или интерфейса, однако вы уже не сможете использовать type hinting и должны будете использовать метод make():


$container->bind('database', MySQLDatabase::class);

$db = $container->make('database');

Для того, чтобы одновременно иметь название класса и короткое имя, вы можете использовать метод alias():


$container->singleton(Cache::class, RedisCache::class);
$container->alias(Cache::class, 'cache');

$cache1 = $container->make(Cache::class);
$cache2 = $container->make('cache');

assert($cache1 === $cache2);

Сохранение произвольного значения


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


$container->instance('database.name', 'testdb');

$db_name = $container->make('database.name');

Также поддерживается array-access синтаксис, который выглядит более привычно:


$container['database.name'] = 'testdb';

$db_name = $container['database.name'];

Это может быть полезно при использовании его с биндингом-замыканием:


$container->singleton('database', function (Container $container) {
    return new MySQLDatabase(
        $container['database.host'],
        $container['database.name'],
        $container['database.user'],
        $container['database.pass']
    );
});

(Сам Laravel не использует контейнер для хранения конфигурации, для этого существует отдельный класс — Config, а вот PHP-DI так делает).


Совет: array-access синтаксис можно использовать для создания объектов вместо метода make():


$db = $container['database'];

Dependency Injection для функций и методов


До сих пор мы использовали DI только для конструкторов, но Laravel также поддерживает DI для произвольных функций:


function do_something(Cache $cache) { /* ... */ }

$result = $container->call('do_something');

Дополнительные параметры могут быть переданы как простой или ассоциативный массив:


function show_product(Cache $cache, $id, $tab = 'details') { /* ... */ }

// show_product($cache, 1)
$container->call('show_product', [1]);
$container->call('show_product', ['id' => 1]);

// show_product($cache, 1, 'spec')
$container->call('show_product', [1, 'spec']);
$container->call('show_product', ['id' => 1, 'tab' => 'spec']);

DI может использован для любых вызываемых методов:


Замыкания

$closure = function (Cache $cache) { /* ... */ };

$container->call($closure);

Статичные методы

class SomeClass
{
    public static function staticMethod(Cache $cache) { /* ... */ }
}

$container->call(['SomeClass', 'staticMethod']);
// or:
$container->call('SomeClass::staticMethod');

Методы объекта

class PostController
{
    public function index(Cache $cache) { /* ... */ }
    public function show(Cache $cache, $id) { /* ... */ }
}

$controller = $container->make(PostController::class);

$container->call([$controller, 'index']);
$container->call([$controller, 'show'], ['id' => 1]);

Сокращения для вызова методов объекта


Container позволяет использовать сокращение вида ClassName@methodName для создания объекта и вызова его метода. Пример:


$container->call('PostController@index');
$container->call('PostController@show', ['id' => 4]);

Контейнер используется для создания экземпляра класса, т.е.:


  1. Зависимости передаются в конструктор класса, а также в вызываемый метод
  2. Вы можете объявить класс как синглтон, если хотите переиспользовать один и тот же объект
  3. Вы можете использовать интерфейс или произвольное имя вместо названия класса

Пример ниже будет работать:


class PostController
{
    public function __construct(Request $request) { /* ... */ }
    public function index(Cache $cache) { /* ... */ }
}

$container->singleton('post', PostController::class);
$container->call('post@index');

Наконец, вы можете передать название "метода по умолчанию" в качестве третьего параметра. Если первым параметром передано название класса и не указано название метода, будет вызвать метод по умолчанию. Laravel использует это в обработчиках событий:


$container->call(MyEventHandler::class, $parameters, 'handle');

// Equivalent to:
$container->call('MyEventHandler@handle', $parameters);

Подмена методов объекта


Метод bindMethod() позволяет переопределить вызов метода, например, для передачи параметров:


$container->bindMethod('PostController@index', function ($controller, $container) {
    $posts = get_posts(...);

    return $controller->index($posts);
});

Все примеры ниже будут работать, при этом будет вызвано замыкание вместо настоящего метода:


$container->call('PostController@index');
$container->call('PostController', [], 'index');
$container->call([new PostController, 'index']);

Однако любы дополнительные параметры, переданные в метод call(), не будут переданы в замыкание и они не могут быть использованы:


$container->call('PostController@index', ['Not used :-(']);

Примечания: метод wrap() не является частью интерфейса Container, он есть только в классе Container. См. [Pull Request(https://github.com/laravel/framework/pull/16800), в котором объясняется, почему параметры не передаются при переопределении.


Биндинг на основе контекста


Может случиться так, что вы захотите иметь разные реализации одного интерфейса в зависимости от места, где он необходим. Ниже показан немного измененный пример из документации Laravel:


$container
    ->when(PhotoController::class)
    ->needs(Filesystem::class)
    ->give(LocalFilesystem::class);

$container
    ->when(VideoController::class)
    ->needs(Filesystem::class)
    ->give(S3Filesystem::class);

Теперь контроллеры PhotoController и VideoController могут зависеть от интерфейса Filesystem, но каждый из низ получит свою реализацию этого интерфейса.


Также можно передать замыкание в метод give(), как вы делаете это в методе bind():


$container
    ->when(VideoController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('s3');
    });

Или можно использовать именованную зависимость:


$container->instance('s3', $s3Filesystem);

$container
    ->when(VideoController::class)
    ->needs(Filesystem::class)
    ->give('s3');

Биндинг параметров к примитивным типам


Помимо объектов, контейнер позволяет производить биндинг примитивных типов (строк, чисел и т.д.). Для этого нужно передать название переменной (вместо названия интерфейса) в метод needs(), а в метод give() передать значение, которое будет подставлено контейнером при вызове метода:


$container
    ->when(MySQLDatabase::class)
    ->needs('$username')
    ->give(DB_USER);

Также мы можем передать замыкание в метод give(), для того, чтобы отложить вычисление значения до тех пор, пока оно не понадобится:


$container
    ->when(MySQLDatabase::class)
    ->needs('$username')
    ->give(function () {
        return config('database.user');
    });

Мы не можем передать в метод give() название класса или именованную зависимость (например, give('database.user')), потому, что оно будет возвращено как есть. Зато мы можем использовать замыкание:


$container
    ->when(MySQLDatabase::class)
    ->needs('$username')
    ->give(function (Container $container) {
        return $container['database.user'];
    });

Добавление тегов к биндингам


Вы можете использовать контейнер для добавления тегов к связанным (по назначению) биндингам:


$container->tag(MyPlugin::class, 'plugin');
$container->tag(AnotherPlugin::class, 'plugin');

И затем получить массив сущностей с указанным тегом:


foreach ($container->tagged('plugin') as $plugin) {
    $plugin->init();
}

Оба параметра метода tag() так же принимают и массив:


$container->tag([MyPlugin::class, AnotherPlugin::class], 'plugin');
$container->tag(MyPlugin::class, ['plugin', 'plugin.admin']);

Rebinding


Примечание: эта возможность контейнера используется довольно редко, поэтому вы можете смело пропустить ее описание.


Коллбэк, зарегистрированный с помощью метода rebinding(), вызывается при изменении биндинга. В примере ниже сессия была заменена уже после того, как она была использована классом Auth, поэтому класс Auth должен быть проинформирован об изменении :


$container->singleton(Auth::class, function (Container $container) {
    $auth = new Auth;
    $auth->setSession($container->make(Session::class));

    $container->rebinding(Session::class, function ($container, $session) use ($auth) {
        $auth->setSession($session);
    });

    return $auth;
});

$container->instance(Session::class, new Session(['username' => 'dave']));

$auth = $container->make(Auth::class);
echo $auth->username(); // dave

$container->instance(Session::class, new Session(['username' => 'danny']));
echo $auth->username(); // danny

Больше информации на эту тему можно найти здесь и здесь.


refresh()


Существует также сокращение, которое может пригодиться в некоторых случаях — метод refresh():


$container->singleton(Auth::class, function (Container $container) {
    $auth = new Auth;
    $auth->setSession($container->make(Session::class));

    $container->refresh(Session::class, $auth, 'setSession');

    return $auth;
});

Оно так же возвращает существующий экземпляр класса или биндинг (если он существует), поэтому вы можете сделать так:


// это сработает, только если вы вызовете методы `singleton()` или `bind()`  с названием класса
$container->singleton(Session::class);

$container->singleton(Auth::class, function (Container $container) {
    $auth = new Auth;

    $auth->setSession($container->refresh(Session::class, $auth, 'setSession'));

    return $auth;
});

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


Примечание: эти методы не являются частью интерфейса Container, они есть только в классе Container.


Overriding Constructor Parameters


Метод makeWith() позволяет вам передать дополнительные параметры в конструктор. При этом игнорируются существующие экземпляры или синглтоны (т.е. создается новый объект). Это может быть полезно при создании объектов с разными параметрами и у которых есть какие-либо зависимости:


class Post
{
    public function __construct(Database $db, int $id) { /* ... */ }
}

$post1 = $container->makeWith(Post::class, ['id' => 1]);
$post2 = $container->makeWith(Post::class, ['id' => 2]);

Note: In Laravel 5.3 and below it was simply make($class, $parameters). It was removed in Laravel 5.4, but then re-added as makeWith() in 5.4.16. In Laravel 5.5 it looks like it will be reverted back to the Laravel 5.3 syntax.


Примечание: В Laravel >=5.3 этот метод называется просто make($class, $parameters). Он был удален в Laravel 5.4, но потом возвращен обратно под названием makeWith в версии 5.4.16. Похоже, что в Laravel 5.5 его название будет снова изменено на make().


Прочие методы


Я описал все методы, которые показались мне полезными, но для полноты картины я опишу оставшиеся доступные методы.


bound()


Метод bound() проверяет, существует класс или алиас, связанный с помощью методов bind(), singleton(), instance() или alias():


if (! $container->bound('database.user')) {
    // ...
}

Также можно использовать метод isset и array-access синтаксис:


if (! isset($container['database.user'])) {
    // ...
}

Значение, указано в методах binding(), instance(), alias() Может быть удалено с помощью unset():


unset($container['database.user']);

var_dump($container->bound('database.user')); // false

bindIf()


Метод bindIf() делает то же самое, что и метод bind(), за тем исключением, что он создает биндинг только если он не существует (см. описание метода bound() выше). Теоретически его можно использовать в пакете для регистрации биндинга по умолчанию, позволяя пользователю переопределить его.


$container->bindIf(Loader::class, FallbackLoader::class);

Не существует метода singletonIf(), вместо этого вы можете использовать bindIf($abstract, $concrete, true):


$container->bindIf(Loader::class, FallbackLoader::class, true);

Или написать код проверки самостоятельно:


if (! $container->bound(Loader::class)) {
    $container->singleton(Loader::class, FallbackLoader::class);
}

resolved()


Метод resolved() возвращает true, если экземпляр класса до этого был создан.


var_dump($container->resolved(Database::class)); // false

$container->make(Database::class);

var_dump($container->resolved(Database::class)); // true

Оно сбрасывается при вызове метода unset() (см. описание метода bound() выше).


unset($container[Database::class]);

var_dump($container->resolved(Database::class)); // false

factory()


Метод factory() возвращает замыкание, которое не принимает параметров и при вызове вызывает метод make().


$dbFactory = $container->factory(Database::class);

$db = $dbFactory();

wrap()


Метод wrap() оборачивает замыкание в еще одно замыкание, которое внедрит зависимости в оборачиваемое при вызове. Метод принимает массив параметров, которые будут переданы в оборачиваемое замыкание; возвращаемое замыкание не принимает никаких параметров:


$cacheGetter = function (Cache $cache, $key) {
    return $cache->get($key);
};

$usernameGetter = $container->wrap($cacheGetter, ['username']);

$username = $usernameGetter();

Примечание: метод wrap() не является частью интерфейса Container, он есть только в классе Container.


afterResolving()


Метод afterResolving() работает точно так же, как и метод resolving(), за тем исключением, что коллбэки, зарегистрированные с его помощью вызываются после коллбэков, зарегистрированных методом resolving().


И наконец…


isShared() – Проверяет, существует ли синглтон/экземпляр для указанного типа
isAlias() – Проверяет, существует ли алиас с указанным названием
hasMethodBinding() – Проверяет, есть ли в контейнере биндинг для указанного метода
getBindings() – Возвращает массив всех зарегистрированных биндингов
getAlias($abstract) – Возращает алиас для указанного класса/биндинга
forgetInstance($abstract) – Удаляет указанный экземпляр класса из контейнера
forgetInstances() – Удаляет все экземпляры классов
flush() – Удаляет все биндинги и созданные экземпляры классов, полностью очищая контейнер
setInstance() – Заменяет объект, возвращаемый getInstance() (подсказка: используйте setInstance(null)для очистки, в последующем будет создан новый экземпляр контейнера)


Примечание: ни один из этих методов не является частью интерфейса Container.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/331982/


Метки:  

Как стать тимлидом и не взорваться

Суббота, 01 Июля 2017 г. 14:38 + в цитатник


Два года назад я начал негласно исполнять роль iOS-lead в компании и формированием стабильной работы iOS отдела. Спустя полгода это трансформировалось в официальную должность. Из-за отсутствия опыта у меня возникало огромное количество проблем, которые вызывали жжение в области нижней части кресла. Это происходило из-за ряда факторов:


  • Нехватка опыта менеджмента.
  • Отсутствие рядом компетентного человека, уже прошедшего путь становления от новой для себя роли к человеку, который понимает устройство процессов, обязанностей и пути решения проблем.
  • Нестабильность общих процессов в компании из-за её молодого возраста на тот момент.

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


Откуда берутся лиды


Исторически сложилось, что существует две классические структурные модели управления компанией.


Первая — вертикальная система управления. Её практикуют реже, к примеру basecamp или 37 signals. Смысл заключается в том, что у вас есть ряд сильных специалистов, способных самостоятельно регулировать свою деятельность.




Вторая — иерархическая система управления.




Есть разработчик, за ним стоит platform lead, за ним CTO, далее CEO. Каждый участник курирует определенный вектор развития. Чем ниже располагается в иерархии человек, тем за более узкоспециализированный вектор он отвечает. Разработчик отвечает лишь за код, который он производит. Lead отвечает целиком за платформу и за её развитие. Технический директор отвечает за техническую составляющую в компании. А генеральный директор — за развитие компании.


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


Оптимальное количество людей, которых может контролировать один человек, сильно варьируется от сферы деятельности и от модели управления. В IT в классической литературе это число колеблется от 5 до 9 человек.




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




Большее ведет к тому, что контроль, обучение и другие функции становится сложнее выполнять. Необходимо добавлять новые роли.




Рассмотрим классическую ситуацию карьерного роста в IT-компании. Когда человек достигает определенного уровня квалификации, он может либо перейти на следующий уровень иерархии при наличии определенных личностных качеств, либо сменить род деятельности/область деятельности/компанию и расти горизонтально дальше. На картинке ниже представлена классическая краткая форма развития. Следующая ступень развития разработчика — team lead либо tech lead. Первая предполагает уход в сторону менеджмента, вторая — глубокий горизонтальный рост специалиста. Team lead дальше уходит в platform lead либо CTO. Из tech lead получаются архитекторы разного калибра.




Роль лида в компании


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


Техническая роль


Говорят, что со временем team lead начинает терять навыки программиста с течением времени. В реальности же большинство основ программирования были сформулированы более 40 лет назад. Порой эти знания более ценные, чем знание конкретного sdk. Но чаще всего наставничество помимо помощи в самых простых программных вопросах сводится к следующему:


Построение экосистемы. Сейчас в IT бизнесе выигрывает та компания, которая предоставляет не просто продукт, а старается построить экосистему. Которая может решать множество задач бизнеса и позволяет пользоваться различными функциями, которые предоставляет компания. Это касается и Apple, и Google, и Facebook. Со временем любой бизнес начинает превращаться в платформу. Похожая ситуация наблюдается и в разработке. Вам необходимо построить для разработчика экосистему целиком, чтобы вопросов возникало как много меньше. Это касается и процессных вещей, и архитектуры. Нужно сформировать документированную систему гайдов, которые позволят новым разработчикам быстро влиться в процесс, а участникам команды не забывать принятые нормы.


Построение системы обучения. Адекватный лид должен грамотно выстраивать процесс обучения всех членов команды, правильно соотнося их собственные интересы с бизнес-задачами компании. Если человек хочет изучить сложный кастомный UI, то задача лида придумать, как это можно использовать во благо. Это касается и других направлений, будь то технические знания или менеджерский бэкграунд. Важно следить за выполнением этих задач, обычно разработчики в своей голове ставят более низкий приоритет данным таскам. Старайтесь помочь в развитии даже в тех областях, в которых у вас меньше знаний. Сводите с нужным специалистом или дайте время разобраться самому, а потом пусть расскажет другим. Так человек укрепит свои знания. Ситуация, когда у разработчика или у вас отсутствуют знания в какой-то области, нормальна. Осознание и принятие этого способствует обучению.




Привитие принципов отношения к коду. Очень важно донести до разработчика мысль, что сам код не стоит ни гроша. Лишь функциональность, которая выполняется этим кодом, имеют смысл. Отсюда и растёт вся экосистема, которую необходимо построить. Важно понимать, что бизнес диктует правила для написания кода, а не наоборот. Если у вас банковская система с крайне высокой надежностью, то TDD вам необходим. Если же вы пишете MVP-приложения, то смысла строить сложную систему и архитектуру на первом этапе попросту нет.


Психологическая роль


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


Организуйте ежемесячные встречи с разработчиками. Сценарий таких встреч каждый месяц повторяется. Поначалу разработчик говорит, что всё ок, всё хорошо, проблем нет. Но основная сила психолога в вопросах. В беседе узнается, что и систему оценки на проекте в прошлом месяце можно улучшить, и взаимоотношения между отделами подтянуть, а ещё было придумано оригинальное решение, которое можно вынести как базовое и написать по нему гайд. Ведь на другом проекте возникли такие же проблемы и решали их дольше, чем нужно. Беседу обязательно надо конспектировать, потому что в ней могут быть отличные мысли. Их можно и нужно претворять в жизнь. После таких бесед часть проблем нивелируется. Так как процесс итеративный, он помогает устранять проблемы и не занимает много времени. Такие встречи не нужно превращать в совещания отделов. Это должны быть именно тет-а-тет беседы в спокойной обстановке. Так вы сможете решить даже сложные личностные проблемы.




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




Внедряйте «безликий код». Самый долгий и трудный процесс — привести код вашего отдела к «безликому коду», когда нельзя по стилю определить, кто писал. Это направление правильное и трудное. Если возможна вариативность решения, вы встретите несколько лагерей. Чтобы в случае кризисной ситуации в ваш адрес не посыпалось «вот решили тогда так, а это оказалось плохо, и сейчас все минусы всплыли», необходимо дипломатично решать каждый вопрос. Помните, что с базовыми решениями, которые вы разрабатываете и согласовываете, будут работать другие люди. Делайте удобно для них.




Как сделать, чтобы стул не сгорел раньше


Классика — когда самый продуктивный разработчик становится лидом. Руководство часто думает, что раз вы делаете фичи быстрее, то можете делать столько же, сколько и средний разработчик, плюс взять обязанности лида. Можете попросить прописать прямо в контракте, какой процент рабочего времени вы будете программировать. Зона ответственности становится совсем другой, ваши обязанности меняются. Есть два варианта развития событий.


  • Начинаете работать намного больше, чтобы успеть и как разработчик, и как team lead. Обычно это ведет к перегоранию.
  • Работаете столько же, сколько и раньше.В итоге не успеете сделать и фичи, и задачи лида.

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


Именно поэтому нужно четко регламентировать, зачем вам программировать, сколько времени и что это даст отделу и в целом компании. Если речь идет о 30% времени, в течение которых вы будете проектировать архитектуру общих решений в компании, библиотек или стандартов — одно дело. Это поможет не заниматься рутинными задачами, не забыть код и смотреть на него более глобально. Но если вам говорят о 70% или 90% времени, то люди просто не понимают, зачем им нужен team lead. Или заранее планируют, что вы будете работать больше 40 часов. Можете либо аргументированно объяснить, как сделать лучше, либо просто ответить отказом. Лучше всего поговорить об ожиданиях.


Составьте план развития. Необходимо сделать четкий план развития себя и отдела хотя бы на ближайший год. Это может быть наработка общих процессов, создание базовых общих решений, ведение курса лекций или создание школы. Всё исходя из потребностей, которые есть у компании сейчас. Если цель — научиться писать код, то уход в сторону архитектуры и мета-принципов. Если заявить о себе на рынке — участие в конференциях и публикации. Если такого плана нет, то начинается ряд проблем:


  • нет плана, нет отчетности по плану;
  • люди не понимают, чем вы занимаетесь;
  • вы и сами не понимаете, чем занимаетесь и что нужно сделать. Находитесь в совершенно неконтролируемом хаосе.



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




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


Делегируйте и отслеживайте выполнение. Необходимо сразу начинать направлять и контролировать процесс выполнения. Что это нам дает? То, что мы перестаем делать кажущиеся важными рутинные задачи путем простого делегирования и можем выполнять задачи, которые и входят в наш план. Плюс различного рода оптимизации в виде CI/CD/статических анализаторов, кодогенераторов, базовых либ и так далее. Всё это экономит нам время в будущем.


Тайм-менеджмент и делегирование


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


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




Контролируйте время. Следите за временем, отведенным на задачу. Речь о тактических и стратегических задачах. Говорю не о ежедневной работе, хотя она и будет являться основным фактором успеха. Под тактическими задачами подразумевается выполнение конкретных больших задачах в виде создания базовых библиотек, задокументированных процессов или процесса обучения. Под стратегическими — определения вектора развития. В какой момент уйти с objective-C? Как сформировать экосистему, чтобы она работала эффективнее? Какие задачи необходимо решить, чтобы эффективность решения задач бизнеса возросла? Это и есть примеры стратегических вопросов. Они наиболее сложные и наиболее длительные, но именно от них зависит ваше завтра.




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


Ошибки, негатив и минусы


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


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


Принятие решений. Бывает, что людей в команду спускают сверху. Вам необходимо сразу четко обозначить здесь правила: либо у вас есть право вето на абсолютно все решения по построению команды, либо необходимо объяснить руководству, почему будет работать именно так. В идеале и самое часто встречающееся на практике — когда team lead сам формирует себе команду. Повышается моральная ответственность так как решения принимал сам lead.


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


К минусам можно также отнести то, что со временем ваши технические навыки будут падать. Это и миф, и правда одновременно. Роль лида позволяет более широко взглянуть на некоторые технические аспекты, на мета-принципы программирования. А то, что вы не будете знать как запрограммировать в iOS 10 новый фреймворк CallKit и какие интерфейсные методы в нём есть — это пережить будет тяжело, но в целом можно.


Coming out


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


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


Построение масштабируемой схемы. Отход от роли «кольца всевластия»


Одна из самых частых негативных историй, которые я слышу от разработчиков про lead'ов — все интересные куски лид забирает себе. От лидов же получаю другой фидбэк, что самая частая проблема — есть задача и её ВООБЩЕ НИКАК!!!111 нельзя делегировать.Отсюда вытекает ряд проблем.


  • Время ресурс ограниченный. Если лид будет делать интересные таски, а не свою работу, то работа будет попросту не сделана.
  • Заключая большинство компетенций внутри одного человека, вы получаете абсолютно немасштабируемую систему. А что самое интересное, получаете фактор автобуса. Конечно, этим можно оперировать при обсуждении зарплаты, но по факту вы не становитесь лучше как лид, а лишь узурпируете власть.
  • Ваша команда не растёт. Никто не понимает установленных процессов, потому что их нет.

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


Варианты будущего роста


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


Что делать


Роль team lead, как и роль менеджера, имеет ряд специфических особенностей, зависящих от внешних и внутренних факторов бизнеса, времени, технологий и видения самой компании. Если стали лидом, первым делом:


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

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


Помните, что учиться можно не только на своём опыте, но и учась на чужих ошибках. Teamleadство — это круто.


Список источников


С. Макконнелл. Сколько стоит программный проект
Дж. Ханк Рейнвотер Как пасти котов
Давид Хейнемейер Ханссон и Джейсон Фрид. Rework

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332056/


Метки:  

[Перевод] Делаем data science-портфолио: история через данные

Суббота, 01 Июля 2017 г. 13:13 + в цитатник
Предисловие переводчика

Перевод внезапно удачно попал в струю других датасайенсных туториалов на хабре. :)
Этот написан Виком Паручури, основателем Dataquest.io, где как раз и занимаются подобного рода интерактивным обучением data science и подготовкой к реальной работе в этой области. Каких-то эксклюзивных ноу-хау здесь нет, но очень подробно рассказан процесс от сбора данных до первичных выводов о них, что может быть интересно не только желающим составить резюме на data science, но и тем, кто просто хочет попробовать себя в практическом анализе, но не знает, с чего начать.


Data science-компании всё чаще смотрят портфолио, когда принимают решение о приёме на работу. Это, в  частности, из-за того, что лучший способ судить о практических навыках — именно портфолио. И хорошая новость в том, что оно полностью в вашем распоряжении: если постараетесь – сможете собрать отличное портфолио, которым будут впечатлены многие компании.


Первый шаг в высококачественному портфолио – это понимание, какие умения в нём надо продемонстрировать.
Основные навыки, которые компании хотят видеть в data scientist-ах, и, соответственно, продемонстрированными в их портфолио, это:


  • Способность общаться
  • Способность сотрудничать с другими
  • Техническая компетенция
  • Способность делать заключения на основе данных
  • Мотивация и способность проявить инициативу.

Всякое хорошее портфолио содержит несколько проектов, каждый из которых может демонстрировать 1-2 данных пункта. Это первый пост из цикла, который будет рассматривать получение гармоничного data science-портфолио. Мы рассмотрим, как сделать первый проект для портфолио, и как рассказать хорошую историю через данные. В конце будет проект, который поможет раскрыть вашу способность общаться и способность делать заключения на основе данных.


Прим. перев.

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


История через данные


В принципе, Data science вся состоит из общения. Вы видите в данных какую-то закономерность, потом ищете эффективный способ объяснить эту закономерность другим, потом убеждаете их предпринять те действия, которые вы считаете нужными. Одно из важнейших умений в data science — наглядно рассказать историю через данные. Удачная история может лучше преподнести ваши догадки и помочь другим понять ваши идеи.
История в контексте data science — это изложение всего того, что вы нашли и того, что это значит. Примером может служить открытие того, что прибыль вашей компании снизилась на 20% за последний год. Просто указать на этот факт недостаточно: надо объяснить, почему прибыль упала и что с этим делать.
Основные компоненты историй в данных это:


  • Понимание и формирование контекста
  • Исследование под разными ракурсами
  • Использование подходящих визуализаций
  • Использование различных источников данных
  • Связное изложение.

Лучшее средство доходчиво рассказать историю через данные — это Jupyter notebook. Если вы с ним незнакомы — тут хороший туториал. Jupyter notebook позволяет интерактивно исследовать данные и публиковать их на различных сайтах, включая гитхаб. Публикация результатов полезна для сотрудничества — ваш анализ смогут расширить другие люди.
Мы в этом посте будем использовать Jupyter notebook вместе с питоновскими библиотеками типа Pandas  и matplotlib.


Выбор темы вашего data science-проекта


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


  • Data.gov — содержит данные правительства
  • /r/datasets — сабреддит с сотнями интересных датасетов
  • Awesome datasets — список датасетов, хостится на гитхабе
  • 17 places to find datasets -пост 17-ю источниками данных и образцами датасетов из каждого

В реальной data science часто не получается найти полностью подготовленный для ваших изысканий датасет. Возможно, придётся агрегировать различные источники данных или серьёзно их чистить.  Если тема вам очень интересна — имеет смысл сделать то же и здесь: лучше покажете себя  в итоге.
Мы для поста будем использовать данные о Нью-Йоркских общеобразовательных школах,  отсюда.


Прим. перев.

На всякий случай, приведу пример более близких к нам (россиянам) аналогичных датасетов:



Выбор темы


Важно сделать весь проект от начала до конца. Для этого полезно ограничить область изучения так, чтобы точно знать, что вы закончите. Проще добавить что-то к уже завершённому проекту, чем пытаться закончить то, что уже попросту надоело доводить до конца.
В нашем случае, мы будем изучать оценки ЕГЭ старшеклассников вместе с различной демографической и прочей информацией о них. ЕГЭ или Единый Государственный экзамен — это тест, который старшеклассники сдают перед поступлением в колледж. Колледжи учитывают оценки, когда принимают решение о зачислении, так что хорошо сдать его — весьма важно. Экзамен состоит из трёх частей, каждая из которых оценивается в 800 баллов. Общий балл в итоге 2400 (хотя иногда это плавало туда-сюда  - в датасете всё по 2400). Старшие школы часто оцениваются по среднему баллу ЕГЭ и высокий средний балл обычно является показателем того, насколько хорош школьный округ.
Были определённые жалобы на несправедливость оценок некоторым нацменьшинствам в США, поэтому анализ по Нью-Йорку поможет пролить свет на справедливость ЕГЭ.
Датасет с оценками ЕГЭ — здесь, а датасет с информацией по каждой школе — здесь. Это будет основой нашего проекта, но нам понадобится ещё информация, чтобы сделать полноценный анализ.


Прим. перев.

В оригинале экзамен называется SAT — Scholastic Aptitude Test. Но поскольку он практически по смыслу идентичен нашему ЕГЭ — решил так его и переводить.


Собираем данные


Как только есть хорошая тема — полезно просмотреть другие датасеты, которые могут расширить тему или помочь углубить исследование. Лучше делать это в начале, чтобы было как можно больше данных для исследования по мере создания проекта. Если данных будет мало — есть шанс, что вы сдадитесь слишком рано.
В нашем случае, есть ещё несколько датасетов по этой теме на том же сайте, которые освещают демографическую информацию и результаты экзаменов.
Вот ссылки на все датасеты, что будем использовать:



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


Сбор вводной информации


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


  • Нью-Йорк поделен на 5 районов, которые являются практически отдельными областями.
  • Школы в Нью-Йорке поделены на несколько школьных округов, каждый может содержать десятки школ.
  • Не все школы в датасете — старшие, так что, возможно, потребуется предварительно почистить данные
  • Каждая школа в Нью-Йорке имеет уникальный код DBN или районно-окружной номер.
  • Агрегируя данные по округам, мы сможем использовать их картографическую информацию, чтобы на карте изобразить отличия между ними.

Прим. перев.

То, что я перевёл как "Районы" на самом деле называется в NYC "боро", и столбцы, соответственно, называются Borough.


Понимаем данные


Чтобы действительно понять контекст данных, нужно потратить время и об этих данных почитать. В нашем случае, каждая вышеприведённая ссылка содержит описание данных для каждой колонки. Похоже, у нас есть данные по оценкам ЕГЭ старшеклассников, вместе с другими датасетами, которые содержат демографическую и прочую информацию.
Запустим кое-какой код, чтобы прочесть данные. Используем Jupyter notebook для наших исследований. Нижеприведённый код:


  • Пробежится по каждому скачанному файлу
  • Прочитает каждый в датафрейм Pandas
  • Положит каждый датафрейм в python-словарь.

import pandas
import numpy as np

files = ["ap_2010.csv", "class_size.csv", "demographics.csv", "graduation.csv", "hs_directory.csv", "math_test_results.csv", "sat_results.csv"]

data = {}
for f in files:
    d = pandas.read_csv("schools/{0}".format(f))
    data[f.replace(".csv", "")] = d

Как только мы всё прочитали, можно использовать на датафреймах метод head, чтобы вывести первые 5 строк каждого:


for k,v in data.items():
    print("\n" + k + "\n")
    print(v.head())

Уже можно видеть в датасетах определённые особенности:
math_test_results


DBN Grade Year Category Number Tested Mean Scale Score Level 1 # \
0 01M015 3 2006 All Students 39 667 2
1 01M015 3 2007 All Students 31 672 2
2 01M015 3 2008 All Students 37 668 0
3 01M015 3 2009 All Students 33 668 0
4 01M015 3 2010 All Students 26 677 6

Level 1 % Level 2 # Level 2 % Level 3 # Level 3 % Level 4 # Level 4 % \
0 5.1% 11 28.2% 20 51.3% 6 15.4%
1 6.5% 3 9.7% 22 71% 4 12.9%
2 0% 6 16.2% 29 78.4% 2 5.4%
3 0% 4 12.1% 28 84.8% 1 3%
4 23.1% 12 46.2% 6 23.1% 2 7.7%

Level 3+4 # Level 3+4 %
0 26 66.7%
1 26 83.9%
2 31 83.8%
3 29 87.9%
4 8 30.8%

ap_2010


DBN SchoolName AP Test Takers Total Exams Taken Number of Exams with scores 3 4 or 5
0 01M448 UNIVERSITY NEIGHBORHOOD H.S. 39 49 10
1 01M450 EAST SIDE COMMUNITY HS 19 21 s
2 01M515 LOWER EASTSIDE PREP 24 26 24
3 01M539 NEW EXPLORATIONS SCI,TECH,MATH 255 377 191
4 02M296 High School of Hospitality Management s s s

sat_results


DBN SCHOOL NAME Num of SAT Test Takers SAT Critical Reading Avg. Score SAT Math Avg. Score SAT Writing Avg. Score
0 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES 29 355 404 363
1 01M448 UNIVERSITY NEIGHBORHOOD HIGH SCHOOL 91 383 423 366
2 01M450 EAST SIDE COMMUNITY SCHOOL 70 377 402 370
3 01M458 FORSYTH SATELLITE ACADEMY 7 414 401 359
4 01M509 MARTA VALLE HIGH SCHOOL 44 390 433 384

class_size


CSD BOROUGH SCHOOL CODE SCHOOL NAME GRADE PROGRAM TYPE CORE SUBJECT (MS CORE and 9-12 ONLY) CORE COURSE (MS CORE and 9-12 ONLY) \
0 1 M M015 P.S. 015 Roberto Clemente 0K GEN ED - -
1 1 M M015 P.S. 015 Roberto Clemente 0K CTT - -
2 1 M M015 P.S. 015 Roberto Clemente 01 GEN ED - -
3 1 M M015 P.S. 015 Roberto Clemente 01 CTT - -
4 1 M M015 P.S. 015 Roberto Clemente 02 GEN E - -

SERVICE CATEGORY(K-9* ONLY) NUMBER OF STUDENTS / SEATS FILLED NUMBER OF SECTIONS AVERAGE CLASS SIZE SIZE OF SMALLEST CLASS \
0 - 19.0 1.0 19.0 19.0
1 - 21.0 1.0 21.0 21.0
2 - 17.0 1.0 17.0 17.0
3 - 17.0 1.0 17.0 17.0
4 - 15.0 1.0 15.0 15.0

SIZE OF LARGEST CLASS DATA SOURCE SCHOOLWIDE PUPIL-TEACHER RATIO
0 19.0 ATS NaN
1 21.0 ATS NaN
2 17.0 ATS NaN
3 17.0 ATS NaN
4 15.0 ATS NaN

demographics


DBN Name schoolyear fl_percent frl_percent \
0 01M015 P.S. 015 ROBERTO CLEMENTE 20052006 89.4 NaN
1 01M015 P.S. 015 ROBERTO CLEMENTE 20062007 89.4 NaN
2 01M015 P.S. 015 ROBERTO CLEMENTE 20072008 89.4 NaN
3 01M015 P.S. 015 ROBERTO CLEMENTE 20082009 89.4 NaN
4 01M015 P.S. 015 ROBERTO CLEMENTE 20092010 96.5

total_enrollment prek k grade1 grade2 ... black_num black_per \
0 281 15 36 40 33 ... 74 26.3
1 243 15 29 39 38 ... 68 28.0
2 261 18 43 39 36 ... 77 29.5
3 252 17 37 44 32 ... 75 29.8
4 208 16 40 28 32 ... 67 32.2

hispanic_num hispanic_per white_num white_per male_num male_per female_num female_per \
0 189 67.3 5 1.8 158.0 56.2 123.0 43.8
1 153 63.0 4 1.6 140.0 57.6 103.0 42.4
2 157 60.2 7 2.7 143.0 54.8 118.0 45.2
3 149 59.1 7 2.8 149.0 59.1 103.0 40.9
4 118 56.7 6 2.9 124.0 59.6 84.0 40.4

graduation


Demographic DBN School Name Cohort \
0 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2003
1 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2004
2 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2005
3 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2006
4 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2006 Aug

Total Cohort Total Grads — n Total Grads — % of cohort Total Regents — n \
0 5 s s s
1 55 37 67.3% 17
2 64 43 67.2% 27
3 78 43 55.1% 36
4 78 44 56.4% 37

Total Regents — % of cohort Total Regents — % of grads ... Regents w/o Advanced — n \
0 s s ... s
1 30.9% 45.9% ... 17
2 42.2% 62.8% ... 27
3 46.2% 83.7% ... 36
4 47.4% 84.1% ... 37

Regents w/o Advanced — % of cohort Regents w/o Advanced — % of grads \
0 s s
1 30.9% 45.9%
2 42.2% 62.8%
3 46.2% 83.7%
4 47.4% 84.1%

Local — n Local — % of cohort Local — % of grad s Still Enrolled — n \
0 s s s s
1 20 36.4% 54.1% 15
2 16 25% 37.200000000000003% 9
3 7 9% 16.3% 16
4 7 9% 15.9% 15

Still Enrolled — % of cohort Dropped Out — n Dropped Out — % of cohort
0 s s s
1 27.3% 3 5.5%
2 14.1% 9 14.1%
3 20.5% 11 14.1%
4 19.2% 11 14.1%

hs_directory


dbn school_name boro \
0 17K548 Brooklyn School for Music & Theatre Brooklyn
1 09X543 High School for Violin and Dance Bronx
2 09X327 Comprehensive Model School Project M.S. 327 Bronx
3 02M280 Manhattan Early College School for Advertising Manhattan
4 28Q680 Queens Gateway to Health Sciences Secondary Sc... Queens

building_code phone_number fax_number grade_span_min grade_span_max \
0 K440 718-230-6250 718-230-6262 9 12
1 X400 718-842-0687 718-589-9849 9 12
2 X240 718-294-8111 718-294-8109 6 12
3 M520 718-935-3477 NaN 9 10
4 Q695 718-969-3155 718-969-3552 6 12

expgrade_span_min expgrade_span_max ... priority02 \
0 NaN NaN ... Then to New York City residents
1 NaN NaN ... Then to New York City residents who attend an ...
2 NaN NaN ... Then to Bronx students or residents who attend...
3 9 14.0 ... Then to New York City residents who attend an ...
4 NaN NaN ... Then to Districts 28 and 29 students or residents

priority03 priority04 priority05 \
0 NaN NaN NaN
1 Then to Bronx students or residents Then to New York City residents NaN
2 Then to New York City residents who attend an ... Then to Bronx students or residents Then to New York City residents
3 Then to Manhattan students or residents Then to New York City residents NaN
4 Then to Queens students or residents Then to New York City residents NaN

priority06 priority07 priority08 priority09 priority10 Location 1
0 NaN NaN NaN NaN NaN 883 Classon Avenue\nBrooklyn, NY 11225\n(40.67...
1 NaN NaN NaN NaN NaN 1110 Boston Road\nBronx, NY 10456\n(40.8276026...
2 NaN NaN NaN NaN NaN 1501 Jerome Avenue\nBronx, NY 10452\n(40.84241...
3 NaN NaN NaN NaN NaN 411 Pearl Street\nNew York, NY 10038\n(40.7106...
4 NaN NaN NaN NaN NaN 160-20 Goethals Avenue\nJamaica, NY 11432\n(40...

  • Большинство содержит столбец  DBN
  • Некоторые поля выглядят интересными для картографирования, в частности Location 1, который содержит координаты в строке.
  • В некоторых датасетах есть по несколько строк на каждую школу (повторяющиеся значения DBN), что намекает на необходимость предварительной обработки.

Приведение данных к общему знаменателю


Чтобы с данными работать было легче, нам надо объединить все датасеты в один — это позволит нам быстро сравнивать колонки в датасетах. Для этого, прежде всего, надо найти общую колонку для объединения. Глядя на то, что нам ранее вывелось, можно предположить, что такой колонкой может быть DBN, поскольку она повторяется в нескольких датасетах.
Если мы загуглим "DBN New York City Schools", то придём сюда, где объясняется, что DBN — это уникальный код для каждой школы. При исследовании датасетов, особенно правительственных, приходится частенько проделать детективную работу, чтобы понять, что значит каждый столбец, даже каждый датасет иногда.
Теперь проблема в том, что два датасета, class_size и hs_directory — не содержат DBN. В hs_directory он называется dbn, поэтому просто переименуем или скопируем его в DBN. Для class_size нужен будет другой подход.
Столбец DBN выглядит так:


In [5]: data["demographics"]["DBN"].head()
Out[5]:
0    01M015
1    01M015
2    01M015
3    01M015
4    01M015
Name: DBN, dtype: object

Если посмотрим на class_size  - вот то, что мы увидим в первых 5 строках:


In [4]:
data["class_size"].head()
Out[4]:

CSD BOROUGH SCHOOL CODE SCHOOL NAME GRADE PROGRAM TYPE CORE SUBJECT (MS CORE and 9-12 ONLY) /
0 1 M M015 P.S. 015 Roberto Clemente 0K GEN ED -
1 1 M M015 P.S. 015 Roberto Clemente 0K CTT -
2 1 M M015 P.S. 015 Roberto Clemente 01 GEN ED -
3 1 M M015 P.S. 015 Roberto Clemente 01 CTT -
4 1 M M015 P.S. 015 Roberto Clemente 02 GEN ED -

CORE COURSE (MS CORE and 9-12 ONLY) SERVICE CATEGORY(K-9* ONLY) NUMBER OF STUDENTS / SEATS FILLED /
0 - - 19.0
1 - - 21.0
2 - - 17.0
3 - - 17.0
4 - - 15.0

NUMBER OF SECTIONS AVERAGE CLASS SIZE SIZE OF SMALLEST CLASS SIZE OF LARGEST CLASS DATA SOURCE SCHOOLWIDE PUPIL-TEACHER RATIO
0 1.0 19.0 19.0 19.0 ATS NaN
1 1.0 21.0 21.0 21.0 ATS NaN
2 1.0 17.0 17.0 17.0 ATS NaN
3 1.0 17.0 17.0 17.0 ATS NaN
4 1.0 15.0 15.0 15.0 ATS NaN

Как можно заметить, DBN — это просто комбинация CSD,BOROUGH и SCHOOL_ CODE. Для незнакомых с Нью-Йорком: он состоит из 5 районов. Каждый район — это организационная единица, приблизительно равная по размеру достаточно большому городу США. DBN расшифровывается как районно-окружной номер. Похоже, что CSD — это округ, BOROUGH — район и в сочетании со SCHOOL_CODE получается DBN.
Теперь, когда мы знаем, как составить DBN, мы можем добавить его в class_size  и  hs_directory.


In 
data["class_size"]["DBN"] = data["class_size"].apply(lambda x: "{0:02d}{1}".format(x["CSD"], x["SCHOOL CODE"]), axis=1)
data["hs_directory"]["DBN"] = data["hs_directory"]["dbn"]

Добавляем опросы


Один из самых потенциально интересных датасетов — это датасет опросов учеников, родителей и учителей по поводу качества школ. Эти опросы включают информацию о субъективном восприятии безопасности каждой школы, учебных стандартах и прочем. Перед тем, как объединять наши датасеты, давайте добавим данные по опросам. В реальных data science-проектах вы часто будете натыкаться на интересные данные по ходу анализа и, возможно, захотите так же их подключить. Гибкий инструмент, типа  Jupyter notebook, позволяет быстро добавить дополнительный код и переделать анализ.
В нашем случае, добавим дополнительные данные по опросам в наш словарь data, после чего объединим все датасеты. Данные по опросам состоят из двух файлов, один на все школы, и один на школьный округ 75. Чтобы их объединить, потребуется написать немного кода. В нём мы сделаем что:


  • Прочитаем опросы по всем школам, используя кодировку windows-1252
  • Прочитаем опросы для округа 75 с использованием windows-1252
  • Добавим флаг, который будет обозначать, для какого округа каждый датасет.
  • Объединим все датасеты в один с помощью метода concat на датафреймах.

In [66]:
survey1 = pandas.read_csv("schools/survey_all.txt", delimiter="\t", encoding='windows-1252')
survey2 = pandas.read_csv("schools/survey_d75.txt", delimiter="\t", encoding='windows-1252')
survey1["d75"] = False
survey2["d75"] = True
survey = pandas.concat([survey1, survey2], axis=0)

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


In [16]:
survey.head()
Out[16]:

N_p N_s N_t aca_p_11 aca_s_11 aca_t_11 aca_tot_11 /
0 90.0 NaN 22.0 7.8 NaN 7.9 7.9
1 161.0 NaN 34.0 7.8 NaN 9.1 8.4
2 367.0 NaN 42.0 8.6 NaN 7.5 8.0
3 151.0 145.0 29.0 8.5 7.4 7.8 7.9
4 90.0 NaN 23.0 7.9 NaN 8.1 8.0

bn com_p_11 com_s_11 ... t_q8c_1 t_q8c_2 t_q8c_3 t_q8c_4 /
0 M015 7.6 NaN ... 29.0 67.0 5.0 0.0
1 M019 7.6 NaN ... 74.0 21.0 6.0 0.0
2 M020 8.3 NaN ... 33.0 35.0 20.0 13.0
3 M034 8.2 5.9 ... 21.0 45.0 28.0 7.0
4 M063 7.9 NaN ... 59.0 36.0 5.0 0.0

t_q9 t_q9_1 t_q9_2 t_q9_3 t_q9_4 t_q9_5
0 NaN 5.0 14.0 52.0 24.0 5.0
1 NaN 3.0 6.0 3.0 78.0 9.0
2 NaN 3.0 5.0 16.0 70.0 5.0
3 NaN 0.0 18.0 32.0 39.0 11.0
4 NaN 10.0 5.0 10.0 60.0 15.0

Мы справимся с этим, заглянув в файл со словарём данных, который мы скачали вместе с данными по опросам. Он расскажет нам про важные поля:
 
А потом мы удалим все не относящиеся к нам столбцы в survey:


In [17]:
survey["DBN"] = survey["dbn"]
survey_fields = ["DBN", "rr_s", "rr_t", "rr_p", "N_s", "N_t", "N_p", "saf_p_11", "com_p_11", "eng_p_11", "aca_p_11", "saf_t_11", "com_t_11", "eng_t_10", "aca_t_11", "saf_s_11", "com_s_11", "eng_s_11", "aca_s_11", "saf_tot_11", "com_tot_11", "eng_tot_11", "aca_tot_11",]
survey = survey.loc[:,survey_fields]
data["survey"] = survey
survey.shape
Out[17]:
(1702, 23)

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


Уплотняем датасеты


Если мы взглянем на некоторые датасеты, включая class_size, мы сразу увидим проблему:


In [18]: data["class_size"].head()
Out[18]:

CSD BOROUGH SCHOOL CODE SCHOOL NAME GRADE PROGRAM TYPE CORE SUBJECT (MS CORE and 9-12 ONLY) /
0 1 M M015 P.S. 015 Roberto Clemente 0K GEN ED -
1 1 M M015 P.S. 015 Roberto Clemente 0K CTT -
2 1 M M015 P.S. 015 Roberto Clemente 01 GEN ED -
3 1 M M015 P.S. 015 Roberto Clemente 01 CTT -
4 1 M M015 P.S. 015 Roberto Clemente 02 GEN ED -

CORE COURSE (MS CORE and 9-12 ONLY) SERVICE CATEGORY(K-9* ONLY) NUMBER OF STUDENTS / SEATS FILLED NUMBER OF SECTIONS AVERAGE CLASS SIZE /
0 - - 19.0 1.0 19.0
1 - - 21.0 1.0 21.0
2 - - 17.0 1.0 17.0
3 - - 17.0 1.0 17.0
4 - - 15.0 1.0 15.0

SIZE OF SMALLEST CLASS SIZE OF LARGEST CLASS DATA SOURCE SCHOOLWIDE PUPIL-TEACHER RATIO DBN
0 19.0 19.0 ATS NaN 01M015
1 21.0 21.0 ATS NaN 01M015
2 17.0 17.0 ATS NaN 01M015
3 17.0 17.0 ATS NaN 01M015
4 15.0 15.0 ATS NaN 01M015

Для каждой школы есть несколько строк (что можно понять по повторяющимся полям DBN и SCHOOL NAME). Хотя, если мы взглянем на sat_results  - в нём только по одной строке на школу:


In [21]:
data["sat_results"].head()
Out[21]:

DBN SCHOOL NAME Num of SAT Test Takers SAT Critical Reading Avg. Score SAT Math Avg. Score SAT Writing Avg. Score
0 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES 29 355 404 363
1 01M448 UNIVERSITY NEIGHBORHOOD HIGH SCHOOL 91 383 423 366
2 01M450 EAST SIDE COMMUNITY SCHOOL 70 377 402 370
3 01M458 FORSYTH SATELLITE ACADEMY 7 414 401 359
4 01M509 MARTA VALLE HIGH SCHOOL 44 390 433 384

Чтобы объединить эти датасеты, нужен способ уплотнить датасеты типа class_size так, чтобы в них было по одной строке на каждую старшую школу. Если не получится — то не получится и сравнить оценки ЕГЭ с размерами класса. Мы сможем этого достичь, получше разобравшись в данных, а потом сделав некоторые агрегации.
По датасету class_size  - похоже, что GRADE  и PROGRAM TYPE содержат разные оценки по каждой школе. Ограничив каждое поле единственным значением, мы сможем отбросить все строчки-дубликаты. В коде ниже мы:


  • Выберем только те значения из class_size, где поле GRADE  - 09-12.
  • Выберем только те значения из class_size, где поле PROGRAM TYPE  - GEN ED.
  • Сгруппируем class_size  по DBN, и возьмём среднее по каждой колонке. По сути, мы найдем средний class_size  по каждой школе.
  • Сбросим индекс, чтобы DBN снова добавился как колонка.

In [68]:
class_size = data["class_size"]
class_size = class_size[class_size["GRADE "] == "09-12"]
class_size = class_size[class_size["PROGRAM TYPE"] == "GEN ED"]
class_size = class_size.groupby("DBN").agg(np.mean)
class_size.reset_index(inplace=True)
data["class_size"] = class_size

Сгущаем остальные датасеты


Дальше нам надо ужать датасет demographics. Данные собраны за несколько лет по одним и тем же школам. Мы выберем только те строки, где поле schoolyear  свежее всего.


In [69]:
demographics = data["demographics"]
demographics = demographics[demographics["schoolyear"] == 20112012]
data["demographics"] = demographics

Теперь нам нужно сжать датасет math_test_results. Он делится по значениям Grade и Year. Мы можем выбрать единственный класс за единственный год:


In [70]:
data["math_test_results"] = data["math_test_results"][data["math_test_results"]["Year"] == 2011]
data["math_test_results"] = data["math_test_results"][data["math_test_results"]["Grade"] == 

Наконец, graduation  тоже надо  уплотнить:


In [71]:
data["graduation"] = data["graduation"][data["graduation"]["Cohort"] == "2006"]
data["graduation"] = data["graduation"][data["graduation"]["Demographic"] == "Total Cohort"]

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


Вычисление агрегированных переменных


Вычисление переменных может ускорить наш анализ возможностью делать сравнивание быстрее и в принципе давая возможность делать некоторые, невозможные без них, сравнения. Первое, что мы можем сделать — посчитать общий балл ЕГЭ из отдельных колонок SAT Math Avg. Score, SAT Critical Reading Avg. Score, и SAT Writing Avg. Score. В коде ниже мы:


  • Преобразуем каждый балл ЕГЭ от строки к числу
  • Сложим все столбцы и получим столбец sat_score, суммарный балл ЕГЭ.

In [72]:
cols = ['SAT Math Avg. Score', 'SAT Critical Reading Avg. Score', 'SAT Writing Avg. Score']
for c in cols:
    data["sat_results"][c] = data["sat_results"][c].convert_objects(convert_numeric=True)

data['sat_results']['sat_score'] = data['sat_results'][cols[0]] + data['sat_results'][cols[1]]

Далее нам надо распарсить координаты каждой школы, чтобы делать карты. Они позволят нам отметить положение каждой школы. В коде мы:


  • Распарсим в столбцы широты и долготы столбец Location 1
  • Преобразуем lat и lon к числам.

Выведем наши датасеты, посмотрим, что получилось:


In [74]:
for k,v in data.items():
    print(k)
    print(v.head())

math_test_results


DBN Grade Year Category Number Tested Mean Scale Score \
111 01M034 8 2011 All Students 48 646
280 01M140 8 2011 All Students 61 665
346 01M184 8 2011 All Students 49 727
388 01M188 8 2011 All Students 49 658
411 01M292 8 2011 All Students 49 650

Level 1 # Level 1 % Level 2 # Level 2 % Level 3 # Level 3 % Level 4 # \
111 15 31.3% 22 45.8% 11 22.9% 0
280 1 1.6% 43 70.5% 17 27.9% 0
346 0 0% 0 0% 5 10.2% 44
388 10 20.4% 26 53.1% 10 20.4% 3
411 15 30.6% 25 51% 7 14.3% 2

Level 4 % Level 3+4 # Level 3+4 %
111 0% 11 22.9%
280 0% 17 27.9%
346 89.8% 49 100%
388 6.1% 13 26.5%
411 4.1% 9 18.4%

survey


DBN rr_s rr_t rr_p N_s N_t N_p saf_p_11 com_p_11 eng_p_11 \
0 01M015 NaN 88 60 NaN 22.0 90.0 8.5 7.6 7.5
1 01M019 NaN 100 60 NaN 34.0 161.0 8.4 7.6 7.6
2 01M020 NaN 88 73 NaN 42.0 367.0 8.9 8.3 8.3
3 01M034 89.0 73 50 145.0 29.0 151.0 8.8 8.2 8.0
4 01M063 NaN 100 60 NaN 23.0 90.0 8.7 7.9 8.1

... eng_t_10 aca_t_11 saf_s_11 com_s_11 eng_s_11 aca_s_11 \
0 ... NaN 7.9 NaN NaN NaN NaN
1 ... NaN 9.1 NaN NaN NaN NaN
2 ... NaN 7.5 NaN NaN NaN NaN
3 ... NaN 7.8 6.2 5.9 6.5 7.4
4 ... NaN 8.1 NaN NaN NaN NaN

saf_tot_11 com_tot_11 eng_tot_11 aca_tot_11
0 8.0 7.7 7.5 7.9
1 8.5 8.1 8.2 8.4
2 8.2 7.3 7.5 8.0
3 7.3 6.7 7.1 7.9
4 8.5 7.6 7.9 8.0

ap_2010


DBN SchoolName AP Test Takers Total Exams Taken Number of Exams with scores 3 4 or 5
0 01M448 UNIVERSITY NEIGHBORHOOD H.S. 39 49 10
1 01M450 EAST SIDE COMMUNITY HS 19 21 s
2 01M515 LOWER EASTSIDE PREP 24 26 24
3 01M539 NEW EXPLORATIONS SCI,TECH,MATH 255 377 191
4 02M296 High School of Hospitality Management s s s

sat_results


DBN SCHOOL NAME Num of SAT Test Takers SAT Critical Reading Avg. Score \
0 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES 29 355.0
1 01M448 UNIVERSITY NEIGHBORHOOD HIGH SCHOOL 91 383.0
2 01M450 EAST SIDE COMMUNITY SCHOOL 70 377.0
3 01M458 FORSYTH SATELLITE ACADEMY 7 414.0
4 01M509 MARTA VALLE HIGH SCHOOL 44 390.0

SAT Math Avg. Score SAT Writing Avg. Score sat_score
0 404.0 363.0 1122.0
1 423.0 366.0 1172.0
2 402.0 370.0 1149.0
3 401.0 359.0 1174.0
4 433.0 384.0 1207.0

class_size


DBN CSD NUMBER OF STUDENTS / SEATS FILLED NUMBER OF SECTIONS \
0 01M292 1 88.0000 4.000000
1 01M332 1 46.0000 2.000000
2 01M378 1 33.0000 1.000000
3 01M448 1 105.6875 4.750000
4 01M450 1 57.6000 2.733333

AVERAGE CLASS SIZE SIZE OF SMALLEST CLASS SIZE OF LARGEST CLASS SCHOOLWIDE PUPIL-TEACHER RATIO
0 22.564286 18.50 26.571429 NaN
1 22.000000 21.00 23.500000 NaN
2 33.000000 33.00 33.000000 NaN
3 22.231250 18.25 27.062500 NaN
4 21.200000 19.40 22.866667 NaN

demographics


DBN Name schoolyear \
6 01M015 P.S. 015 ROBERTO CLEMENTE 20112012
13 01M019 P.S. 019 ASHER LEVY 20112012
20 01M020 PS 020 ANNA SILVER 20112012
27 01M034 PS 034 FRANKLIN D ROOSEVELT 20112012
35 01M063 PS 063 WILLIAM MCKINLEY 20112012

fl_percent frl_percent total_enrollment prek k grade1 grade2 \
6 NaN 89.4 189 13 31 35 28
13 NaN 61.5 328 32 46 52 54
20 NaN 92.5 626 52 102 121 87
27 NaN 99.7 401 14 34 38 36
35 NaN 78.9 176 18 20 30 21

... black_num black_per hispanic_num hispanic_per white_num \
6 ... 63 33.3 109 57.7 4
13 ... 81 24.7 158 48.2 28
20 ... 55 8.8 357 57.0 16
27 ... 90 22.4 275 68.6 8
35 ... 41 23.3 110 62.5 15

white_per male_num male_per female_num female_per
6 2.1 97.0 51.3 92.0 48.7
13 8.5 147.0 44.8 181.0 55.2
20 2.6 330.0 52.7 296.0 47.3
27 2.0 204.0 50.9 197.0 49.1
35 8.5 97.0 55.1 79.0 44.9

graduation


Demographic DBN School Name Cohort \
3 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2006
10 Total Cohort 01M448 UNIVERSITY NEIGHBORHOOD HIGH SCHOOL 2006
17 Total Cohort 01M450 EAST SIDE COMMUNITY SCHOOL 2006
24 Total Cohort 01M509 MARTA VALLE HIGH SCHOOL 2006
31 Total Cohort 01M515 LOWER EAST SIDE PREPARATORY HIGH SCHO 2006

Total Cohort Total Grads — n Total Grads — % of cohort Total Regents — n \
3 78 43 55.1% 36
10 124 53 42.7% 42
17 90 70 77.8% 67
24 84 47 56% 40
31 193 105 54.4% 91

Total Regents — % of cohort Total Regents — % of grads ... Regents w/o Advanced — n \
3 46.2% 83.7% ... 36
10 33.9% 79.2% ... 34
17 74.400000000000006% 95.7% ... 67
24 47.6% 85.1% ... 23
31 47.2% 86.7% ... 22

Regents w/o Advanced — % of cohort Regents w/o Advanced — % of grads \
3 46.2% 83.7%
10 27.4% 64.2%
17 74.400000000000006% 95.7%
24 27.4% 48.9%
31 11.4% 21%

Local — n Local — % of cohort Local — % of grads Still Enrolled — n \
3 7 9% 16.3% 16
10 11 8.9% 20.8% 46
17 3 3.3% 4.3% 15
24 7 8.300000000000001% 14.9% 25
31 14 7.3% 13.3% 53

Still Enrolled — % of cohort Dropped Out — n Dropped Out — % of cohort
3 20.5% 11 14.1%
10 37.1% 20 16.100000000000001%
17 16.7% 5 5.6%
24 29.8% 5 6%
31 27.5% 35 18.100000000000001%

hs_directory


dbn school_name boro \
0 17K548 Brooklyn School for Music & Theatre Brooklyn
1 09X543 High School for Violin and Dance Bronx
2 09X327 Comprehensive Model School Project M.S. 327 Bronx
3 02M280 Manhattan Early College School for Advertising Manhattan
4 28Q680 Queens Gateway to Health Sciences Secondary Sc... Queens

building_code phone_number fax_number grade_span_min grade_span_max \
0 K440 718-230-6250 718-230-6262 9 12
1 X400 718-842-0687 718-589-9849 9 12
2 X240 718-294-8111 718-294-8109 6 12
3 M520 718-935-3477 NaN 9 10
4 Q695 718-969-3155 718-969-3552 6 12

expgrade_span_min expgrade_span_max ... priority05 priority06 priority07 priority08 \
0 NaN NaN ... NaN NaN NaN NaN
1 NaN NaN ... NaN NaN NaN NaN
2 NaN NaN ... Then to New York City residents NaN NaN NaN
3 9 14.0 ... NaN NaN NaN NaN
4 NaN NaN ... NaN NaN NaN NaN

priority09 priority10 Location 1 \
0 NaN NaN 883 Classon Avenue\nBrooklyn, NY 11225\n(40.67...
1 NaN NaN 1110 Boston Road\nBronx, NY 10456\n(40.8276026...
2 NaN NaN 1501 Jerome Avenue\nBronx, NY 10452\n(40.84241...
3 NaN NaN 411 Pearl Street\nNew York, NY 10038\n(40.7106...
4 NaN NaN 160-20 Goethals Avenue\nJamaica, NY 11432\n(40...

DBN lat lon
0 17K548 40.670299 -73.961648
1 09X543 40.827603 -73.904475
2 09X327 40.842414 -73.916162
3 02M280 40.710679 -74.000807
4 28Q680 40.718810 -73.806500

Объединяем датасеты


После всей подготовки наконец, мы можем объединить все датасеты по столбцу DBN. В итоге у нас получится датасет с сотнями столбцов, из всех исходных. При объединении важно отметить, что в некоторых датасетах нет тех школ, что есть в датасете sat_results. Чтобы это обойти, нам надо объединять датасеты через outer join, тогда мы не потеряем данные. В реальном анализе отсутствие данных — обычное дело. Продемонстрировать возможность исследовать и справляться с таким отсутствием — важная часть портфолио.
Про разные типы джоинов можно почитать здесь.
В коде ниже мы:


  • Пройдёмся по всем элементам словаря data
  • Выведем количество неуникальных DBN в каждом
  • Решим, как будем джоинить — внутренне или внешне.
  • Объединим элемент с датафреймом full через столбец DBN.

In [75]:
flat_data_names = [k for k,v in data.items()]
flat_data = [data[k] for k in flat_data_names]
full = flat_data[0]
for i, f in enumerate(flat_data[1:]):
    name = flat_data_names[i+1]
    print(name)
    print(len(f["DBN"]) - len(f["DBN"].unique()))
    join_type =

Метки:  

VK Streaming API Contest

Суббота, 01 Июля 2017 г. 13:00 + в цитатник
Привет! Мы запустили бета-тестирование нового продукта для получения публичных данных из ВКонтакте в реальном времени — Streaming API.



Он призван заменить методы публичного API для поиска и парсинга данных (newsfeed.search, wall.search, wall.get) и стать более удобным решением задачи аналитики упоминаний в соцсети.

Прочитать подробнее о том, как это работает, можно в документации API.

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

Конкурс продлится до 31 июля, приём работ начнётся 24 июля. Для участия в конкурсе нужно зарегистрироваться по этой ссылке.

Победителей ждут ценные призы (Macbook Pro, iPhone 7 Plus или iPad Pro 12.9”), купоны на таргетированную рекламу ВКонтакте и шанс присоединиться к нашей команде.

Узнать подробнее о конкурсе и задать свои вопросы Вы можете в сообществе ВКонтакте API.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332054/


Метки:  

Отвечаем на вопросы читателей: что такое когнитивная система IBM Watson, и как она работает?

Суббота, 01 Июля 2017 г. 12:27 + в цитатник

Александр Дмитриев

Добрый день, Хабрахабр! Сегодня о том, что собой представляет когнитивная система Watson и как она работает расскажет Александр Дмитриев, бизнес-консультант Клиентского центра IBM в Москве. Он ответит на вопросы, которые возникали у читателей по прочтении других материалов на эту тему.

Александр, на Хабре наши читатели регулярно задают вопросы, основной посыл которых можно уложить в один: «Что такое когнитивная система IBM Watson, и как она работает?» Помогите, пожалуйста, ответить на него.

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

При обработке информации устанавливаются связи и корреляции между самыми разными данными, событиями, фактами и явлениями. Одна из главных задач системы — выявление связей, которые незаметны простому глазу и которые не могут быть выявлены обычным способом, либо сделать это стандартными методами сложно. К примеру, если у какого-нибудь предприятия есть система мониторинга, которая выдает тысячи меняющихся параметров за минуту, то ни один аналитик не в состоянии проанализировать такую информацию оперативно. Инструментарий Watson же это позволяет сделать. Это если говорить об IBM Watson в целом.



Я видел вопросы относительно того, является ли Watson искусственным интеллектом. Отвечаю — нет, это не искусственный интеллект. Это – своеобразный усилитель естественного интеллекта человека, который позволяет быстрее обрабатывать информацию, охватывать большие объемы данных и находить то, что проходит мимо человеческого взгляда.

Станислав Лем писал об этом в своей книге «Сумма технологии»: «Человек непосредственно не может соперничать с Природой: она слишком сложна, чтобы он мог ей в одиночку противостоять. Образно говоря, человек должен построить между собой и Природой целую цепь из звеньев, в которой каждое последующее звено будет как усилитель Разума более мощным, чем предыдущее».

Как это делается и зачем? Существует система аналитики верхнего уровня на базе Watson и системы аналитики более мелких уровней. Последние фактически представляют собой поисковые и аналитические системы с определенной спецификой. Они решают прикладные задачи. Как это работает? Заливаем большое количество информации определенной тематики в виде файлов распространенных форматов типа xls и csv. Загружаем эти данные в облако, после чего система Watson Analytics приступает к анализу этой информации, находя корреляции самостоятельно – с минимальным участием оператора. Это и есть небольшое, но очень важное отличие от других систем, поскольку здесь не просто поиск по ранее загруженным данным. Подчеркну – система сама анализирует загруженную информацию.

Что значит — сама? Система так настроена, что она просматривает все загруженные данные, проводит их очистку, указывая на технические проблемы вроде несовпадения форматов, пробелы, пропуски. Человек выбрасывает все экстремумы, которые являются ошибками или поводом для отдельного рассмотрения, выбирает метод обработки. Далее система занимается анализом данных, ищет корреляции, находит наиболее сильные и показывает оператору несколько гипотез с корреляциями, скажем, от 0,3 до 0,8.

Эти задачи я бы назвал условно задачами нижнего уровня. Они предназначены для того, чтобы упростить и ускорить работу аналитика. Рутинные операции автоматизируются самой системой. Это если говорить о Watson как системе поиска корреляций в массивах больших данных. То, на что шести аналитикам потребуется около недели, система IBM Watson Analytics через облако делает примерно за два часа. Насколько сложно работать с Watson? Я однажды провел эксперимент, усадив за систему людей, более-менее разбирающихся в статистике. Они в первый раз видели интерфейс. Через полтора часа они уже самостоятельно и весьма активно работали с ней.

Верхний уровень — это большие системы, внедрение которых требует значительного времени (от полугода и выше). Их принцип работы основан на идее исследования Gartner, в котором говорится, что к 2030 году общая квалификация специалистов в большинстве индустриальных отраслей существенно снизится. Один из факторов этого можно пояснить. Дело в том, что специалист, который привык (и которому по долгу службы нужно) постоянно пользоваться справочной информацией, уже не считает нужным помнить все то, что помнили специалисты «старой закалки» (от высоты стратосферы до точки кипения меди). Новое поколение охотно прибегает к интернету как к справочнику и не держит все необходимые знания в голове. Получается, что специалист становится в определенной степени зависим от машинных систем, а общий уровень его квалификации соответственно понижается. Это раз.



Второе, зачем нужны такие сложные системы? У нефтедобывающих и многих других корпораций огромная проблема — это передача информации от «поколения к поколению». Например, предыдущим коллективом сотрудников накоплен архив очень ценной технической информации. Но вот проблема — его никто в состоянии прочитать. Ведь на это нужно огромное количество времени. Специалисту для того, чтобы с этой информацией, ознакомиться, потребуется несколько лет — это человек должен читать сутками напролет без еды и отдыха.

Так что, обучение новых работников – дорогостоящая проблема. Смена кадров на крупных предприятиях – тут могут быть сотни и тысячи специалистов в год. Ушел человек — и с ним ушел бесценный опыт и знания. Как передать опыт? Путем записей? О них мы говорили выше.

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

В чем же задача системы Watson верхнего уровня?

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

После этого настраиваются алгоритмы, которые позволяют анализировать информацию, детализировать ее, выделять и строить дерево актуальных тем — для той же «нефтянки» это наладка оборудования, разработка пластов, статистика, тенденции в технологиях и т.п. Все это собирается по темам, создается система подсказок. Системы такого рода, разработанные специалистами IBM, уже функционируют в ряде корпораций, включая австралийскую компанию Woodside Petroleum.



Сказанное выше можно проиллюстрировать примером. Есть главный инженер на предприятии, он дает задание пробурить скважину в пласте, по которому есть актуальные данные. Тот, кому дали задание, обращается к системе на естественном языке: «Что нужно сделать, чтобы пробурить скважину в таком пласте до такой-то глубины?» И система дает ответ, она работает как подсказка по конкретной нефтяной задаче. Сконфигурированная для «нефтянки» система делает подборку документации с выводами, и «говорит» — вот раньше поступали так, но при этом возникали такие-то проблемы, которые можно решить вот так. Вот это и есть система Watson — она подсказывает то, что человеку нужно делать в конкретном случае, выступает в качестве помощника.

Может ли система Watson работать онкологом, метеорологом, кем-то еще?

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

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

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



Каким образом IBM Watson работает с естественным языком? Может ли система понимать контекст литературного произведения?

Однозначно, да. Но вопрос — зачем? Еще вопрос — кому это нужно, и кто за это платит? При работе с языком в плане обработки литературного произведения необходимо рассматривать текст в связи с историческим контекстом самой работы. Система может все понимать, если ей поставить такую задачу, включая произведения О. Генри, переводы которого лучше всего получались у Корнея Чуковского. Нужно сказать, что системы, работающие с языком, так же настраиваются и обучаются. В простейшем случае это тривиальный парсинг, то есть разбор очистка текста от излишней информации. Что касается Watson, то это, в первую очередь, создание словарей разных языков. Систему в любом случае нужно обучать с прицелом на конкретную задачу.

Я лично участвовал в проекте эмоционального анализа. Сегодня Watson улавливает эмоциональную окраску текста. Например, она научилась определять иронию. В целом, здесь снова речь заходит о выявлении корреляций. Что касается той же иронии — ее придумали еще древние греки. Кажется, любой человек распознает ее по каким-то определенным признакам. Если машину научить улавливать эти признаки, она тоже научится определять иронию.

Повторюсь, возможности системы определяются актуальностью решаемой задачи. В основном, в помощи IBM Watson нуждаются крупные компании, которым вряд ли нужно в первую очередь определять иронию в отчетах своих сотрудников (хотя и такое, наверное, бывает). А вот для них, в случае необходимости, мы настраиваем систему таким образом, чтобы она могла определять отношение пользователей/покупателей к брендам и продуктам компаний.



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

В целом, Watson решает конкретные задачи. Делать система может многое, что именно — определяется в зависимости от общей постановки задачи и ее направленности.

Вопрос — границы возможности системы. Возьмем пример — если взять того же О. Генри, можно ли настроить Watson на литературный перевод произведений этого автора, и сколько времени на это потребуется? Скажем, это понадобилось издательству, которое готово за это платить.

Ответ — однозначно можно. Но я не могу ответить точно, сколько времени на это потребуется. Это вопрос усилий и вложений.

Любая специализированная система Watson, будь то «медик», «финансовый аналитик» или «инженер» требует участия специалистов. В этом случае я бы набирал команды лучших лингвистов по теории и практике языка. Часть команд будет составлять словари, идиомы, искать данные по текстам, корреляции между русскими словами и английскими. Зачем? Одно слово на любом языке может значить очень многое. В словари и будут попадать такие слова, с указанием максимально обширного спектра их знания.

После этого нужно приступить к решению второй задачи. А именно — загнать в базу тексты переводов О. Генри, которые считаются наиболее качественными и удачными. Затем Watson для поиска подходящих по смыслу слов будет использовать методику оценки корреляций с максимальными значениями. Выбирать система будет различные варианты перевода от простого к сложному (слова, фразы, предложения и т.д.). В ходе этого процесса понадобятся группы экспертов, которые будут дополнительно обучать систему. Они будут корректировать переводы, проводить тонкую настройку для того, чтобы после такого обучения система Watson начала давать действительно хорошие переводы. Это так и работает — первый перевод будет не очень хорошим, второй — получше, а потом — очень хорошим. Большой плюс Watson в том, что систему можно донастраивать, благодаря обратной связи. Ведь без обратной связи система просто потеряет управление. Обратная связь позволяет системе в динамике, в ходе работы уточнять и корректировать основную цель. В нашем случае обратную связь обеспечивают специалисты в предметной области. Если это нефтяная компания, как Woodside, то лучшие эксперты будут отмечать лучшие, удачные ответы системы, и система это будет запоминать, постепенно улучшая качество выдаваемых рекомендаций. Так что у Watson есть еще одно преимущество. Если большинство систем со временем устаревает и требует переделки, то данная система со временем только набирается опыта и становится еще мощнее.

Еще вопрос — есть ли какие-то задачи, которые Watson не может решить сейчас ни при каких условиях?

Есть очень важный аспект — этический. Часть задач нерешаема потому, что существующие вопросы выходят за рамки технических систем. В качестве примера можно привести робомобиль. Грубо говоря – на кого или во что наедет автомобиль, если невозможно избежать столкновения, но есть выбор – скажем, в стену, пожилого человека или беременную женщину? Водитель-человек ведь все равно сделает свой выбор. Но машина — нет, она выбор сделать не может, поскольку этот вопрос пока не решен ни юридически, ни этически. И в машину эти знания и правила поведения в экстремальных ситуациях просто пока нельзя заложить. Это первый класс задач, пока не решаемых, поскольку не решен целый ряд этических, юридических, социальных и других проблем, связанных с самими задачами.



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

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

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

Каким вы видите IBM Watson в будущем?

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

Я думаю, что это будет универсальная система подсказок, которая сможет отвечать на широчайший круг вопросов и которая станет нам привычной. Причем отвечать она будет не так, как современные поисковики — не просто давать ссылки на источники в интернете, а предоставлять рекомендации с указанием источника информации и используемых методик.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332070/


Метки:  

Автоматизация блокирования Petya/NonPetya

Суббота, 01 Июля 2017 г. 12:03 + в цитатник
Коллеги, доброго времени суток.

В связи с шумихой вокруг Petya/NonPetya, моим коллегой Владиславом Ковалевым был разработан скрипт на PowerShell для борьбы с вредителем, за что ему огромное спасибо. Надеюсь кому-то будет полезен. Если кому интересно, прошу под кат

Скрипт petya_youshellnotpass выполняет следующее:

— создает правила в фаерволе, блокирующее уязвимые порты;
— ищет в C:\Windows файлы perfc и удаляет при обнаружении;
— создает новые файлы perfc и устанавливает на них запрет для всех;
— ищет в папке Temp для каждого пользователя exe-файлы и выводит список найденных, нужно просмотреть и удалить подозрительные файлы (вручную, самим)

Правило запуска:
Выполняется в обычном режиме Windows (не безопасном и не PE).

1.Запустить powershell console от админа и прописать:
Set-executionpolicy unrestricted -force

2.Выполнить скрипт petya_youshellnotpass. Внимательно следить за выводом скрипта. Проверить файлы, которые он найдет в папке Temp.

3.В powershell console ввести команду:
Set-executionpolicy restricted -force

Код:

# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
 
# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
 
# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
   {
   # We are running "as Administrator" - so change the title and background color to indicate this
   $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
   $Host.UI.RawUI.BackgroundColor = "DarkBlue"
   clear-host
   }
else
   {
   # We are not running "as Administrator" - so relaunch as administrator
   
   # Create a new process object that starts PowerShell
   $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
   
   # Specify the current script path and name as a parameter
   $newProcess.Arguments = $myInvocation.MyCommand.Definition;
   
   # Indicate that the process should be elevated
   $newProcess.Verb = "runas";
   
   # Start the new process
   [System.Diagnostics.Process]::Start($newProcess);
   
   # Exit from the current, unelevated, process
   exit
   }

$Compname = Get-WmiObject -Class win32_computersystem | select -expa name
$Cred = $Compname+"\admin"

Write-Verbose -Message "Start process" -Verbose
Write-Verbose -Message "Adding firewall rule" -Verbose

try{New-NetFirewallRule -Action Block -Description Peta.A -Direction Inbound -DisplayName Peta.A_Block -Profile Any -Protocol TCP -LocalPort 135,139,445,1024-1035}
catch{netsh advfirewall firewall add rule name="Petya.A_Block" protocol=TCP dir=in localport=135,139,445,1024-1035 action=block}



if((Test-Path -Path C:\Windows\perfc) -eq $true)
{
    try
    {
    Remove-Item -Path C:\Windows\perfc -Force -ea Stop
    Write-Verbose -Message "File perfc was already exist" -Verbose
    }
    catch {Write-Verbose -Message "File perfc already fixed" -Verbose}
}

if((Test-Path -Path C:\Windows\perfc.dll) -eq $true)
{
    try
    {
    Remove-Item -Path C:\Windows\perfc.dll -Force -ea Stop
     Write-Verbose -Message "File perfc.dll was already exist" -Verbose
     }
      catch {Write-Verbose -Message "File perfc.dll already fixed" -Verbose}
}

if((Test-Path -Path C:\Windows\perfc.dat) -eq $true)
{
    try
    {
    Remove-Item -Path C:\Windows\perfc.dat -Force -ea stop
    Write-Verbose -Message "File perfc.dat was already exist" -Verbose
    }
     catch {Write-Verbose -Message "File perfc.dat already fixed" -Verbose}
}

try{
New-item -Path C:\Windows -ItemType File -Name Perfc -Force -ea Stop
New-item -Path C:\Windows -ItemType File -Name Perfc.dll -Force -ea Stop
New-item -Path C:\Windows -ItemType File -Name Perfc.dat -Force -ea stop
}catch{Write-Verbose -Message "Dont need to create new files"}

Write-Verbose -Message "Successfully created" -Verbose
$acl1 = Get-acl  C:\Windows\Perfc
$acl2 = Get-acl  C:\Windows\Perfc.dll
$acl3 = Get-acl  C:\Windows\Perfc.dat

$acl1.SetAccessRuleProtection($true,$true)
$acl2.SetAccessRuleProtection($true,$true)
$acl3.SetAccessRuleProtection($true,$true)

$accrule1 = New-Object System.Security.AccessControl.FileSystemAccessRule("NT AUTHORITY\SYSTEM","FullControl","Deny")
$accrule2 = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Администраторы","FullControl","Deny")
$accrule3 = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Администраторы","ReadAndExecute","Allow")
$accrule4 = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Администраторы","ReadAndExecute","Allow")

$acl1.SetAccessRule($accrule1)
$acl1.SetAccessRule($accrule2)
$acl1.SetAccessRule($accrule3)
$acl1.SetAccessRule($accrule4)

$acl2.SetAccessRule($accrule1)
$acl2.SetAccessRule($accrule2)
$acl2.SetAccessRule($accrule3)
$acl2.SetAccessRule($accrule4)

$acl3.SetAccessRule($accrule1)
$acl3.SetAccessRule($accrule2)
$acl3.SetAccessRule($accrule3)
$acl3.SetAccessRule($accrule4)

Set-Acl -AclObject $acl1 -Path C:\Windows\Perfc -ea SilentlyContinue
Set-Acl -AclObject $acl2 -Path C:\Windows\Perfc.dll -ea SilentlyContinue
Set-Acl -AclObject $acl2 -Path C:\Windows\Perfc.dat -ea SilentlyContinue

Write-Verbose -Message "Searching for exe files in temp" -Verbose

$Prof= Get-ChildItem -Path "C:\Users" -Force |where {!($_.Name -like "Все пользователи")-or!($_.Name -like "Public")}| select -expa fullname

[array]$TempFiles = $null
[array]$TempPath = $nell

Foreach ($P in $Prof)
{

$TempPath = $P+"\AppData\Local"
Get-ChildItem -Path "$TempPath" -Force -Recurse -ErrorAction SilentlyContinue | where {$_.name -like "*.exe"} | select name,fullname | Format-Table -HideTableHeaders

}

if ($TempFiles -eq $null){Write-Verbose -Message "None exe file was found" -Verbose}
else{Write-Warning -Message "$TempFiles" -Verbose}



Write-Host "Press any key to continue ..."

$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332068/


Метки:  

Как запутать аналитика. Часть вторая: что такое моделирование предметной области?

Суббота, 01 Июля 2017 г. 11:51 + в цитатник
В прошлой статье я говорил о заблуждениях, к которым склонны программисты и обещал рассказать про заблуждения, к которым склонны не только программисты, но и каждый из нас.

Объект учета и результат его классификации (существительные)


Проведем мысленный эксперимент. Представьте себе два хранилища моделей. В одном хранилище созданы классы для хранения моделей плавательных средств, в другом – классы для хранения моделей автомобилей. Допустим, что есть объект, который в одном хранилище описан как объект класса плавсредство, а во второй – как объект класса автомобиль. Допустим, что стоит задача объединения этих хранилищ в одно. Как вы это сделаете?

Возможно, вы создадите подкласс автомобилей, который одновременно является подклассом класса плавсредств, добавите модель в созданный вами подкласс и удалите, теперь ставшие лишними, модель из класса автомобилей и модель из класса плавсредств.
Возможно, вы удалите объект из класса плавсредств, а объект из класса автомобилей одновременно сделаете объектом класса плавсредств.

Как бы вы не поступили, у меня вопрос: как теперь вы назовете моделируемый объект? Автомобиль-плавсредство? А, если потом появятся другие классы, к которым относится этот объект? Будете перечислять их названия через дефис, и говорить: автомобиль-плавсредство-альфа-омега? Если задать этот вопрос аналитику, то ответом будет: это один и тот же объект, одновременно являющийся и автомобилем и плавсредством. Буквально это значит, что есть объект учета, который может быть одновременно классифицирован и как автомобиль, и как плавсредство. Из этого следует, что мы моделируем не автомобили и плавсредства, а объекты учета!

  • Создание объекта в хранилище – это моделирование объекта учета
  • Помещение созданного объекта в класс – это его классификация.

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

Сравните высказывания:

  1. Это- дерево
  2. Это- объект учета, классифицированный как дерево.

Второе высказывание корректное, но слишком длинное, чтобы им пользоваться на практике.

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

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

Объект учета и результат его классификации (прилагательные)


Когда мы говорим, что машина красная, мы предполагаем, что машина может обладать свойством – быть черной, красной и тд. Но могут ли на самом деле машины иметь свойства?

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

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

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

Что бы вы ни сделали, важно понять, что полученный атрибут «цвет» не будет принадлежать ни автомобилям ни плавсредствам. Это нечто, что существует вне этих классов. И это правильно, поскольку атрибут не связан с типом. Атрибут – это способ деления множества объектов на подмножества другим способом, отличным от типа. Об этом я писал ранее в статье Понятия: множество, тип, атрибут.
.
Поэтому, если быть строгим, нельзя говорить о свойстве, принадлежащем объекту, надо говорить о свойстве независимо от типа, или объекта, например, так:

Объект учета отнесен к классу красных объектов, то есть классифицирован.

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

  1. Красный автомобиль
  2. Объект учета относится к классу автомобилей и к классу красных объектов

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

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

Смысл процесса классификации


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

На скорую руку приходит решение о создании новой модели, которая будет моделировать операцию под названием «купля-продажа», исполнителем которой будут Мартынов и Гаврилов. Но тут возникает вопрос: куда делась операция по продаже, исполнителем которой был Мартынов, и куда делась операция по покупке, исполнителем которой был Гаврилов? В новой модели эта информация оказалась потерянной, да еще и возникла коллизия, ведь операция – это такое действие, которое должно иметь цель. У продажи цель есть – продать подороже, и ради нее работал Мартынов. У покупки есть цель – купить подешевле, и ради нее работал Гаврилов. А у купли-продажи нет цели, потому что у нее нет стейкхолдера. Как же сохранить информацию, которая была в хранилищах до их объединения, избежать коллизии и, в то же время, объединить модели операций?

Для этого нам надо поступить так же, как мы поступили с автомобилями. Надо создать модель объекта учета и отнести его к двум разным классам одновременно: к классу операций по продаже и к классу операций по покупке. В разных классах будет один атрибут «исполнитель», значения которого будут зависеть от того, какой класс мы рассматриваем: класс покупок, или класс продаж. И это неожиданно – оказывается, что значения атрибутов одного объекта учета могут зависеть от того, как мы классифицировали объект учета.

Это кажется странным, но здесь нет ничего удивительного. Разные люди могут классифицировать один и тот же объект по-разному. Кто-то считает, что это – машина, а кто-то считает, что это – плавсредство. Для кого-то это — операция по продаже, а для кого-то – операция по покупке. Теперь проясняется смысл классификации. Классификация объекта учета – это выражение определенной точки зрения на объект учета. Нет автомобилей, но есть объект учета и субъект, трактующий этот объект учета как автомобиль. Нет операции по продаже, есть субъект, который трактует объект учета как операцию по продаже.

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

Поэтому тезисы про автомобиль и плавсредство надо уточнить:

  1. Объект учета с точки зрения Иванова классифицирован как плавсредство.
  2. Тот же объект учета с точки зрения Сидорова отнесен к классу плавсредств.
  3. Тот же объект с точки зрения Иванова и Сидорова отнесен к классу красных объектов.

Говорить что-либо об объекте учета без ссылки на субъект, который провел описание этого объекта, не имеет смысла. Это как-бы очевидно, но почему-то аналитики об этом забывают.

О том, как при помощи OWL можно построить хранилище, в котором будут учтены разнообразные точки зрения, рассказано в статье: Multi-viewpoint Ontologies for Decision-Making Support.

Выводы


Моделирование предметной области – это моделирование объектов учета путем их классификации. Классификация всегда субъективна и потому требует указания на субъект, проведшего классификацию.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332064/


Метки:  

Как мы хомяка яблоками кормили или эффективный backend на Go для iOS

Суббота, 01 Июля 2017 г. 11:47 + в цитатник


Как и обещал, рассказываю о том, как мы мигрировали свой бэкенд на Go и смогли уменьшить объем бизнес логики на клиенте более, чем на треть.


Для кого: небольшим компаниям, Go и мобильным разработчикам, а также всем, кто в тренде или просто интересуется данной тематикой.
О чем: причины перехода на Go, с какими сложностями столкнулись, а также инструкции и советы по улучшению архитектуры мобильного приложения и его бэкенда.
Уровень: junior и middle.



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



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


Так получилось, что у нас тогда в команде был довольно сильный Java(Spring) разработчик, и мы решили каждому новому заказчику твердо объявлять: мы сами пишем бэкенд, либо ищите кого-нибудь другого. Поначалу боялись, что такая принципиальная позиция будет отпугивать, а мы в итоге останемся голыми на хлебе и воде. Но как оказалось, что если мы уже понравились кому-то на этапе переговоров и с нами хотят работать, то практически обо всем можно договориться. Даже когда у клиента в команде уже есть свои люди, которых он изначально планировал задействовать. Тогда-то мы узнали такое умное слово как микросервисы, и что можно делать отдельный сервак с бизнес логикой, выполняющий задачи строго для мобильного приложения. Не буду спорить, что такой подход не везде уместен, но речь дальше пойдет не об этом.


Причины перехода на Go


После нескольких успешных проектов Java оказалась слишком тяжелой для нас. Много времени уходило на рутину, чтобы сделать все максимально удобно для приложения.
Не хочу сказать ничего плохого о Spring и Java в целом, это удивительный инструмент под серьезные задачи, как огромный толстопузый испанский галеон. А мы искали что-то больше похожее на легковесный пиратский клипер.
Нам надо было быстро внедрять фичи, легко их менять и не греть голову в поисках самого оптимального решения в каждой ситуации. Знаете, как это бывает, когда долго гуглишь на предмет типового решения твоей задачи, чтобы оно было наиболее подходящим, потом оказывается, что из 10 из них 5 уже устарели. А потом еще тратишь полчаса на выбор названия для переменной.


У Go такой проблемы нет. От слова совсем. Иногда даже через слишком: сидишь, ищешь идеальное решение, а StackOverflow тебе на это отвечает: 'Ну да, просто циклом for, а ты чего ждал?'
Со временем к этому привыкаешь и перестаешь гуглить всякие мелочи по пустякам, а начинаешь включать голову и просто писать код.


Какие возникли сложности


Начнем с того, что там нет наследования. Поначалу это просто выносило мозг. Приходится ломать все свое представление об ООП и привыкать к [утиной типизации].(https://ru.wikipedia.org/wiki/%D0%A3%D1%82%D0%B8%D0%BD%D0%B0%D1%8F_%D1%82%D0%B8%D0%BF%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F) Формулируя простыми словами: если это выглядит как утка, плавает как утка и крякает как утка, то это, возможно, и есть утка.
По сути, есть только интерфейсное наследование.


А во-вторых, из существенных минусов, — малое количество готовых инструментов, но зато много багов. Многие вещи нельзя сделать привычным способом, а кое-что вообще отсутствует как класс. Например, нет нормального фреймворка для IoC (инверсия зависимостей). Опытные гоферы скажут, что есть либа от Facebook. Но может я просто не умею ее готовить, либо все-таки ее удобство на самом деле оставляет желать лучшего. Оно просто не может сравниться со Spring и потому приходится много работать руками.


Еще из небольших ограничений Go в целом, например, нельзя сделать API вида:


/cards/:id
/cards/something

Так как для существующего http роутера — это взаимоисключающие запросы. Он путается между wildcard переменной и конкретным адресом something. Глупое ограничение, но приходится с этим жить. Если же кто-то знает решение, то буду рад услышать.


Также отсутствует hibernate или более менее адекватные аналоги. Да, есть множество ORM, но все они пока довольно слабые. Лучшее, что я встретил за время разработки на Go — это gorm. Ее главное преимущество — это удобнейший маппинг ответа от базы в структуру. А запросы придется писать на голом sql, если не хотите провести долгие часы за отладкой сомнительного поведения.


P.S. Хочу отдельно поделиться workaround-ом, который возник в процессе работы с этой либой. Если вам нужно записать id после insert в какую-то переменную с помощью gorm, а не в структуру как обычно, то поможет следующий костыль. На уровне запроса переименовываем результат returning на любой другой, отличный от id:


... returning id as value

С последующим сканом в переменную:


... Row().Scan(&variable)

Оказывается, дело в том, что поле 'id' воспринимается gorm-ом как конкретное поле объекта. И чтобы развязаться, нужно на уровне запроса ее переименовать во что-нибудь другое.


Плюсы или почему мы все-таки пишем на Go


Хочется начать с порога вхождения: он минимален. Вспоминая какой скрежет вызывал в освоении тот же Spring, то Go, по сравнению с ним, можно преподавать в младших классах, настолько он прост.


И эта простота заключается не только в языке, но и в окружении, которое он за собой несет.
Вам не нужно читать долгие маны по gradle и maven, не потребуется писать длиннющие конфиги, чтобы все хотя бы просто один раз запустилось. Здесь все обходится парой команд, а достойный сборщик и профилировщик уже является частью языка и не требует глубокого исследования для старта.
Как говорится: easy to learn, hard to master. Это то, чего мне всегда лично не хватало в современных технологиях: их как будто делают не для людей.


Из этого же следует скорость разработки. Язык был сделан для одной цели:


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


Архитектура


Web


В качестве web framework остановили свой выбор на Gin. Есть еще Revel, но нам он показался слишком узким и непоколебимо диктующим свою парадигму. Мы же предпочитаем чуть больше развязанные руки, чтобы можно было быть гибкими.


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


Конечно, и он не без проблем. Некоторые решения, например, кэш, сделаны третьей стороной. И происходит конфликт импортов, если вы у себя привыкли использовать import через github, а у них сделано через gopkg и наоборот. В итоге два плагина могут быть просто взаимоисключающими.
Если кто-то знает решение этой проблемы, то напишите, пожалуйста, в комментариях.


Менеджер зависимостей


Долго писать не буду, а сразу скажу, что это без сомнений Glide. Если вы работали с gradle или maven, то вам наверняка знакома парадигма объявлений зависимостей в неком файле с последующим их задействованием по необходимости. Так вот Glide — это хомячий Gradle, с решением конфликтов и прочими плюшками.


Кстати, если у вас возникнут проблемы при тестировании, когда go test лезет в папку vendor, жадно тестируя каждую либу, то проблема решается элементарно:


go test $(glide novendor)

Этот параметр исключает папку vendor из тестирования.
В сам репозиторий достаточно положить glide.yaml и glide.lock файлы.


Мобильной разработке это все никак не поможет, но просто, чтобы вы знали)


ORM и Realm


Это будет объемный раздел о передаче и хранении данных с бэкенда на клиент. Начнем с Go, плавно переходя на мобильную платформу.


А что такое Realm и чем он лучше CoreData/Массивов/SQLite?

Если вы никогда не сталкивались с Realm и не понимаете о чем речь, то правильно раскрыли спойлер.


Realm — это мобильная база, которая облегчает работу с синхронизацией данных на протяжении всего приложения. В ней нет таких проблем как в CoreData, где постоянно приходится работать в контекстах, даже когда объект еще никуда не сохранен. Легче соблюдать консистентность.
Достаточно просто создать сущность и работать с ней как с обычным объектом, передавая между потоками и жонглируя ею как душе угодно.


Множество операций она делает за вас, но, конечно, и у нее есть косяки: отсутствует не привязанный к регистру поиск, в целом поиск не доделан нормально, потребляет памяти как не в себя (особенно на android), отсутствует группировка как в FRC, и так далее.


Мы посчитали, что стоит мириться с этими проблемами и оно того стоит.


Чтобы не повторяться, коротко скажу, что мы в качестве orm используем Gorm и дам пару рекомендаций:


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

Наверное, это все применимо к любой технологии, тут я немного скапитанил, но все же. Лишний раз напомнить не повредит, это важно.


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

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


Почему Go?

У вас может справедливо возникнуть вопрос: при чем тут вообще Go, если одинаковые названия можно сделать в любом языке? И будете правы, но одним из преимуществ Go является легчайшее форматирование чего угодно через рефлексию и структуры. Пусть рефлексия есть во многих языках, но в Go с ней работать проще всего.


Кстати, если вам нужно спрятать из ответа пустую структуру, то наилучший способ — это перегрузить метод MarshalJSON у структуры:


// Допустим, надо скрыть пустое поле Pharmacy у объекта Object
func (r Object) MarshalJSON() ([]byte, error) {
    type Alias Object
    var pharmacy *Pharmacy = nil
// Если id != 0, то используем значение. Если нет - ставим nil
    if r.Pharmacy.ID != 0 {
        pharmacy = &r.Pharmacy
    }
    return json.Marshal(&struct {
        Pharmacy           *Pharmacy `json:"pharmacy,omitempty"`
        Alias
    }{
        Pharmacy:           pharmacy,
        Alias:              (Alias)(r),
    })
}

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


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


func (c *Comment) MarshalJSON() ([]byte, error) {
    type Alias Comment
    return json.Marshal(&struct {
        CreatedAt string `json:"createdAt"`
        *Alias
    }{
        CreatedAt: c.CreatedAt.Format(time.RFC3339),
        Alias:  (*Alias)(c),
    })
}

А на клиенте это делается через форматирование даты по следующему шаблону:


"yyyy-MM-dd'T'HH:mm:ssZ"

Еще одним преимуществом RFC3339 является то, что он выступает форматом даты по умолчанию для Swagger. И сама по себе отформатированная таким образом дата, довольно читаема для человека, особенно по сравнению с posix time.


На клиенте же (пример для iOS, но на Android аналогично), при идеальном совпадении названий всех полей и отношений класса, сохранение можно сделать одним генерик методом:


func save(dictionary: [String : AnyObject]) -> Promise{
    return Promise {fulfill, reject in
        let realm = Realm.instance
       // Если у вас есть дата в словаре, то здесь надо ее отформатировать перед записью.
       // Так как реалм не умеет сохранять дату в виде строго.
        try! realm.write {
            realm.create(T.self, value: dictionary, update: true)
        }
        fulfill()
    }
}

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


array.forEach { object in
    try! realm.write {
        realm.create(T.self, value: object, update: true)
    }
}

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


try! realm.write {
    array.forEach { object in
        realm.create(T.self, value: object, update: true)
    }
}

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


Кстати, немного отходя от темы. Если вам не нужно иметь персистентную базу данных на клиенте, то это не повод отказываться от Realm. Он позволяет работать с собой строго в оперативной памяти, сбрасывая свое наполнение по первому требованию.
Ссылки для iOS и Android.
Такой подход позволяет использовать все преимущества реактивной базы данных и вышеописанный маппинг.


Еще хочу добавить для особо внимательных к мелочам: здесь нет утверждения, что Go — это единственно верное решение и панацея для мобильной разработки. Каждый может решать эту задачу по-своему. Мы выбрали этот путь.


Структура Go проекта


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


Теперь мы подошли к самому интересному, если вы Go разработчик. Допустим, вы пишите бэкенд для некоего типового приложения: у вас есть слой с REST API, некая бизнес логика, модель, логика работы с базой данных, утилиты, скрипты миграции и конфиг с ресурсами. Вам как-то все это надо увязать у себя в проекте по классам и папочкам, соблюсти принципы SOLID и, желательно, не сойти с ума при этом.
Пока накидаем абстрактно, не погружаясь слишком глубоко, но так, чтобы была понятна общая структура. Если будет интересно, то посвящу этому полноценный отдельный материал. Все-таки сейчас речь идет о мобильном приложении в связке с Go.


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


Начнем со скриншота нашей структуры:

(До чего милые в Intellij Idea хомячки, не правда ли? Каждый раз умиляюсь)
В неразвернутых директориях содержатся сразу Go файлы, либо файлы ресурсов. Проще говоря, все раскрыто так, чтобы видеть максимальное погружение.


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


Итак, по порядку:


Web
В вебе хранится все, что отвечает за обработку запросов: байндеры, фильтры и контроллеры — а вся их спайка происходит в api.go. Пример такого склеивания:


regions := r.Group("/regions")
regions.GET("/list", Cache.Gin, rc.List)
regions.GET("/list/active", Cache.Gin, regionController.ListActive)
regions.GET("", binders.Coordinates, regionController.RegionByCoord)

Там же происходит инициализация контроллеров и инъекция зависимостей. По сути весь api.go файл состоит из метода Run, где формируется и стартуется роутер, и кучи вспомогательных методов по созданию контроллеров со всеми зависимостями и их групп.


Web.Binders
В папке binders располагаются биндеры, которые парсят параметры из запросов, конвертируют в удобный формат и закидывают в контекст для дальнейшей работы.
Пример метода из этого пакета. Он берет параметр из query, конвертирует в bool и кладет в контекст:


func OpenNow(c *gin.Context)  {
    openNow, _ := strconv.ParseBool(c.Query(BindingOpenNow))
    c.Set(BindingOpenNow, openNow)
}

Самый простой вариант без обработки ошибок. Просто для наглядности.


Web.Controllers
Обычно на уровне контроллеров делают больше всего ошибок: напихают лишней логики, забудут про интерфейсы и изоляцию, а потом вообще скатятся к функциональному программированию. Вообще в Go контроллеры страдают от той же болезни, что и в iOS: их постоянно перенагружают. Поэтому сразу определим, какие задачи они должны выполнять:


  • получать параметры запроса;
  • вызывать соответствующий метод сервиса;
  • отправлять ответ об успехе или ошибке с изменением форматирования по необходимости.
    Необходимость — это, например, когда сервис логично возвращает числом id некоего объекта, нет ничего криминального в том, что контроллер обернет его в map перед отправкой:
    c.IndentedJSON(http.StatusCreated, gin.H { "identifier": m.ID })

Возьмем какой-нибудь пример типового контроллера.


Класс, если опускать импорты, начинается с интерфейса контроллера. Да-да, соблюдаем букву 'D' в слове SOLID, даже если у вас всегда будет только одна реализация. Это значительно облегчает тестирование, давая возможность подменять сам контроллер на его mock:


type Order interface {
    PlaceOrder(c *gin.Context)
    AroundWithPrices(c *gin.Context)
}

Далее у нас идет сама структура контроллера и его конструктор, принимающий в себя зависимости, который мы будем вызывать при создании контроллера в api.go:


// С маленькой буквы, чтобы наружу ничего не вываливалось
type order struct {
    service services.Order
}

func NewOrder(service services.Order) Order {
    return &order {
        service: service,
    }
}

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


func (o order)PlaceOrder(c *gin.Context) {
    m := c.MustGet(BindingOrder).(*model.Order)
    o.service.PlaceOrder(m)
    c.IndentedJSON(http.StatusCreated, gin.H {
        "identifier": m.ID,
    })
}

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


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


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


func NewOrder(repo repositories.Order, txFactory TransactionFactory) Order {
    return &order { repo: repo, txFactory: txFactory }
}

Фабрика транзакций — это просто класс, генерирующий транзакции, здесь ничего сложного:


type TransactionFactory interface {
    BeginNewTransaction() Transaction
}

Полный код фабрики для gorm
type TransactionFactory interface {
    BeginNewTransaction() Transaction
}

type transactionFactory struct {
    db *gorm.DB
}

func NewTransactionFactory(db *gorm.DB) TransactionFactory {
    return &transactionFactory{db: db}
}

func (t transactionFactory)BeginNewTransaction() Transaction {
    tx := new(transaction)
    tx.db = t.db
    tx.Begin()
    return tx
}

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


type Transaction interface {
    Begin()
    Commit()
    Rollback()
    DataSource() interface{}
}

Полный код транзакции для gorm
type Transaction interface {
    Begin()
    Commit()
    Rollback()
    DataSource() interface{}
}

type transaction struct {
    Transaction
    db *gorm.DB
    tx *gorm.DB
}

func (t *transaction)Begin() {
    t.tx = t.db.Begin()
}

func (t *transaction)Commit() {
    t.tx.Commit()
}

func (t *transaction)Rollback() {
    t.tx.Rollback()
}

func (t *transaction)DataSource() interface{} {
    return t.tx
}

Если с begin, commit, rollback все должно быть понятно, то Datasource — это просто костыль для доступа к низкоуровневой реализации, потому что работа с любой БД в Go устроена так, что транзакция является просто копией акссессора к базе со своими измененными настройками. Он нам понадобится позже при работе в репозиториях.


Собственно, вот и пример работы с транзакциями в методе сервиса:


func (o order)PlaceOrder(m *model.Order)  {
    tx := o.txFactory.BeginNewTransaction()
    defer tx.Commit()
    o.repo.Insert(tx, m)
}

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


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


Экспертам

Знаю, что нет управления уровнями изоляции.
Если нашли еще какие косяки — пишите в комментах.


В качестве дополнительного совета юниорам, хочу сказать, что транзакция должна быть открыта минимально возможное время. Постарайтесь подготовить все данные так, чтобы на период между begin и commit приходилось минимальное количество логики и вычислений.
Бывает, что транзакцию открывают и идут курить, отправляя, например запрос в гугл. А потом удивляются, почему это с дедлоком зафакапилось все.


Интересный факт
Во многих современных базах данных, deadlock определяется максимально просто: по таймауту. При большой нагрузке сканировать ресурсы на предмет определения блокировки — дорого. Поэтому часто вместо этого используется обычный таймаут. Например, в mysql. Если не знать эту особенность, то можно подарить себе чудеснейшие часы веселой отладки.

Repositories
Тоже самое: интерфейс, структура, конструктор, который, как правило, уже без параметров.
Просто приведу пример операции Insert, которую мы вызывали в коде сервиса:


func (order)Insert(tx Transaction, m *model.Order) {
    db := tx.DataSource().(*gorm.DB)
    query := "insert into orders (shop_id) values (?) returning id"
    db.Raw(query, m.Shop.ID).Scan(m)
}

Получили из транзакции низкоуровневый модификатор доступа, составили запрос, выполнили его. Готово.


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


Приложение


Ладно, хомяки это мило, но как теперь с этим работать на клиенте?


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


Стек


Сетевой слой:
Alamofire для Swift проектов и AFNetworking для Objective-C.
Кстати, а вы знали, что Alamofire — это и есть AFNetworking? Префикс AF значит Alamofire, в чем можно убедиться, заглянув в лицензию AFNetworking:


Замыкания:
Многие в качестве callback-ов для бизнес логики передают в параметры запросов блоки для успеха/провала или лепят один на все. В итоге в параметрах каждого метода бизнес логики висит толстенное замыкание или даже не одно, что не сказывается положительно на читаемости проекта.
Иногда блок отдают в качестве возвращаемого значения, что тоже неудобно.


Есть такая замечательная вещь как промисы. Реализация iOS: PromiseKit. Простыми словами — вместо кучи блоков, передаваемых в метод, вы возвращаете объект, который потом можно развернуть не только в success/failure замыкания, но еще и некий always, вызывающийся всегда, независимо от успеха/провала метода.
Их также можно чередовать, объединять и делать множество приятных вещей.


Как по мне, так ключевое преимущество — это именно последовательное применение. Можно разделить flow бизнес логики на маленькие операции, вызываемые друг за другом. В итоге, например, метод получения детализации для некого товара, будет выглядеть так:


func details(id: Int) -> Promise {
    return getDetails(id)
                .then(execute: parse)
                .then(execute: save)
}

А так внутренний метод getDetails, просто делающий запрос на конкретный адрес:


func getDetails(id: Int) -> Promise> {
    return Promise { fulfill, reject in
        Alamofire.request(NetworkRouter.drugDetails(id: id)).responseJSON { fulfill($0) }
    }
}

Парсинг и сохранение

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


func parseAsDictionary(response: DataResponse) -> Promise<[String:AnyObject]> {
    return Promise {fulfill, reject in
        switch response.result {
        case .success(let value):
            let json = value as! [String : AnyObject]
            guard response.response!.statusCode < 400 else {
                let error = Error(dictionary: json)
                reject(error)
                return
            }
            fulfill(json)
            break
         case .failure(let nserror):
            let error = Error(error: nserror as NSError)
            reject(error)
            break
       }        
    }
}

// Выше этот метод уже был, но продублриюу
func save(items: [[String : AnyObject]]) -> Promise {
    return Promise {fulfill, reject in
        let realm = Realm.instance
        try! realm.write {
           items.forEach { item in
               // Замените на свой класс или сделайте generic
               realm.create(Item.self, value: item, update: true)
           }
       }
       fulfill(items.count)
    }
}

А в самом контроллере, если вы используете MVC, все максимально просто:


_ = service.details().then {[weak self] array -> Void in 
    // Success. Do w/e you like.
}

База данных
Вопрос про хранение данных описывал выше, когда говорил про работу с ORM на Go-side, поэтому повторяться не буду, только добавлю ссылку на то как получать уведомления об обновлениях базы в том же контроллере. По сути если в БД что-то добавилось, то контроллер асинхронно об этом узнает. Это гораздо удобнее, чем каждый раз мучиться с подсчетом datasource при каждом малейшем движении. А если еще и эти изменения могут произойти не только из одного места, то вообще швах.


Сюда перетаскивать кусок кода из гайда по fine-grained notifications не буду, дабы не плодить копипасту.


Для extra-ленивых
class ViewController: UITableViewController {
  var notificationToken: NotificationToken? = nil

  override func viewDidLoad() {
    super.viewDidLoad()
    let realm = try! Realm()
    let results = realm.objects(Person.self).filter("age > 5")

    // Observe Results Notifications
    notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
      guard let tableView = self?.tableView else { return }
      switch changes {
      case .initial:
        // Results are now populated and can be accessed without blocking the UI
        tableView.reloadData()
        break
      case .update(_, let deletions, let insertions, let modifications):
        // Query results have changed, so apply them to the UITableView
        tableView.beginUpdates()
        tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
                           with: .automatic)
        tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
                           with: .automatic)
        tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
                           with: .automatic)
        tableView.endUpdates()
        break
      case .error(let error):
        // An error occurred while opening the Realm file on the background worker thread
        fatalError("\(error)")
        break
      }
    }
  }

  deinit {
    notificationToken?.stop()
  }
}

Взаимодействие внутри проекта


Многие разработчики болеют гигантоманией, которая вызывает у них желание запихнуть всю бизнес логику в один файл с названием ApiManager.swift. Или есть более латентные формы, когда этот файл делят на много других, где каждый — это extension от ApiManager, что на самом деле совсем не лучше.
Получается перегруженный божественный класс и к тому же singleton, отвечающий просто за все. Я сам раньше так работал, когда занимался мелкими приложениями, но на крупном проекте это здорово аукнулось.


Лечится это с помощью SOA (service oriented architecture). Есть отличное видео от Rambler, где подробнейшим образом разбирается, что это такое и с чем его едят, но я постараюсь на пальцах дать инструкцию по внедрению в проект.


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


Пример таких сервисов в одном из проектов:


Где в каждом сервисе находится ограниченный набор методов, за которые он отвечает и с которыми работает. Желательно, чтобы сервис не превышал 200-300 строк. Если он перевалил за этот объем, то значит бедняга выполняет совсем не одну задачу, которая ему предназначена.


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


Заключение


Подытожу. Хорошо подготовленные данные на бэкенде в связке с Realm-ом на mobile-side дают возможность практически целиком отказаться от дополнительной бизнес логики на клиенте, сводя все к работе с интерфейсом. Можно не согласиться, но по-моему, так и должно быть. Ведь любой клиент, пусть даже и такой классный как iOS или Android, — это в первую очередь вью вашего продукта!


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


Но вот что я хочу сказать. Своей работой я стараюсь подтянуть общий уровень разработчиков в сообществе.
Вы можете сказать, что это чересчур смело. Но знаете, как становится грустно, когда на собеседование приходит человек, считающий себя классным специалистом или, по крайней мере, сильным мидлом, но при этом даже не знающий простейших вещей? Который путается между MVP и MVVM и похоже, что вообще разрабатывает интуитивно.


Из серии, когда заглядываешь человеку в код, видишь там несуразную лютую жесть и спрашиваешь его: “Вася, почему ты так сделал?” А он отвечает: “Хз, иначе не работало.”
Про архитектуру даже не заикаюсь.


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


P.S. Прикладываю опрос. Стоит ли делать в будущих статьях титульную информацию с кратким сюжетом и целевой аудиторией? Нигде такого не видел, но мне кажется, могло бы быть полезно. Да, есть тэги, но они, на мой взгляд, недостаточно заметны и не раскрывают сути.

Стоит ли делать протокол в начале статьи?

Проголосовало 6 человек. Воздержалось 6 человек.

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

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/331456/


Метки:  

Вероятностный и информационный анализ результатов измерений на Python

Суббота, 01 Июля 2017 г. 11:38 + в цитатник

Метки:  

Petya и другие. ESET раскрывает детали кибератак на корпоративные сети

Суббота, 01 Июля 2017 г. 10:20 + в цитатник
Эпидемия шифратора Petya в центре внимания. Проблема в том, что это лишь последний инцидент в серии атак на украинские компании. Отчет ESET раскрывает некоторые возможности Diskcoder.C (он же ExPetr, PetrWrap, Petya или NotPetya) и включает информацию о ранее неосвещенных атаках.



TeleBots


В декабре 2016 года мы опубликовали исследование деструктивных атак, выполненных кибергруппой, которую мы называем TeleBots. Группировка атаковала финансовые учреждения и использовала версию деструктивного компонента KillDisk для Linux. Кроме того, TeleBots могут иметь отношение к группе BlackEnergy, связанной с кибератаками на энергетические компании.

Вредоносная программа KillDisk использовалась группой TeleBots на заключительном этапе атак, чтобы перезаписать файлы с определенными расширениями на диске жертвы. Забегая вперед, выкуп никогда не был приоритетом для этой группы.

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


Рисунок 1. Изображение, которое выводил на экран KillDisk в ходе первой волны атак в декабре 2016 года.

Во второй волне атак злоумышленники доработали KillDisk, добавив шифрование и контактную информацию в сообщение о выкупе, что придавало сходство с типичной программой-вымогателем. При этом авторы запросили за восстановление данных рекордную сумму – 222 биткоина (около 250 тысяч долларов по нынешнему курсу). Это может указывать на то, что хакеры не были заинтересованы в получении выкупа, а стремились нанести ущерб атакуемым компаниям.


Рисунок 2. Требование выкупа KillDisk, версия второй волны атак в декабре 2016 года.

В 2017 году группа TeleBots продолжила атаки, которые стали более изощренными. С января по март 2017 года группа скомпрометировала украинскую компанию, разрабатывающую программное обеспечение (не M.E.Doc) и, используя VPN-туннели, получила доступ к внутренним сетям нескольких финансовых учреждений.


Рисунок 3. Атаки на цепи поставок (supply-chain attacks) в 2017 году

В ходе этой атаки TeleBots пополнили арсенал двумя образцами шифраторов и обновленными версиями инструментов, упомянутых в наших предыдущих отчетах.

Первый бэкдор, на который в значительной степени полагалась группа, – Python/TeleBot.A, который был переписан с языка программирования Python на Rust. Функции не изменились – это стандартный бэкдор, который использует Telegram Bot API, чтобы получать команды от операторов и отправлять ответы.


Рисунок 4. Дизассемблированный код Win32/TeleBot.AB trojan.

Второй бэкдор, написанный на VBS и упакованный с помощью программы script2exe, был сильно обфусцирован, но его функциональность осталась такой же, как в прежних атаках.


Рисунок 5. Обфусцированная версия VBS-бэкдора.

На этот раз VBS-бэкдор использует командный C&C-сервер 130.185.250[.]171. Чтобы сделать соединения менее подозрительными для тех, кто проверяет журналы фаервола, атакующие зарегистрировали домен transfinance.com[.]ua и разместили его на этом IP-адресе. Как видно на рисунке 6, также был запущен почтовый сервер с именем severalwdadwajunior, который работал в сети Tor.


Рисунок 6. Информация о сервере группы TeleBots.

Кроме того, атакующие использовали следующие инструменты:
  • CredRaptor (кража паролей)
  • Plainpwd (модифицированный Mimikatz используется для восстановления учетных данных Windows из памяти)
  • SysInternals’ PsExec (используется для распространения угрозы внутри сети)

Как сказано ранее, на завершающей стадии атаки TeleBots распространяют шифратор, используя PsExec и украденные учетные данные Windows. Антивирусные продукты ESET детектируют его как Win32/Filecoder.NKH. После выполнения малварь шифрует все файлы (за исключением расположенных в C:\Windows) с применением алгоритмов AES-128 и RSA-1024. Вредоносная программа добавляет к зашифрованным файлам расширение .xcrypted

Когда шифрование завершено, программа создает текстовый файл readme.txt со следующим содержанием:

Please contact us: openy0urm1nd@protonmail.ch

Помимо вредоносного ПО для Windows, группа TeleBots использовала Linux-шифратор для других ОС. ESET детектирует угрозу как Python/Filecoder.R, она написана на Python. На этот раз атакующие используют для шифрования файлов сторонние утилиты, такие как openssl. Шифрование осуществляется с помощью алгоритмов RSA-2048 и AES-256.


Рисунок 7. Код на Python Linux-шифратора Python/Filecoder.R, используемого группой TeleBots.

В коде скрипта на Python атакующие оставляют свой комментарий, включающий следующий текст:

feedback: openy0urm1nd[@]protonmail.ch


Win32/Filecoder.AESNI.C


18 мая мы зафиксировали активность шифратора другого семейства – Win32/Filecoder.AESNI.C, также известного как XData.

Программа-вымогатель распространялась преимущественно на Украине, что связано с интересным начальным вектором заражения. По данным телеметрии ESET, шифратор появлялся на компьютере сразу после запуска программного обеспечения для отчетности и документооборота M.E.Doc, широко распространенного в украинских компаниях.

Функционал Win32/Filecoder.AESNI.C позволял шифратору автоматически распространяться в локальной сети компании. В частности, встроенная DLL Mimikatz использовалась для извлечения учетных записей Windows из памяти скомпрометированного компьютера. С помощью учетных данных малварь распространялась внутри сети, используя утилиту PsExec.

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

ESET выпустила дешифратор для жертв Win32/Filecoder.AESNI.

Эпидемия Diskcoder.C (более известного как Petya)


Что действительно получило широкое освещение в СМИ, так это эпидемия Petya, начавшаяся 27 июня. Вредоносная программа скомпрометировала множество систем в критических инфраструктурах и корпоративных сетях на Украине и за ее пределами.

Шифратор, который используется в этой атаке, может подменять главную загрузочную запись (MBR) собственным вредоносным кодом. Код позаимствован у программы-вымогателя Win32/Diskcoder.Petya, поэтому некоторые исследователи называют угрозу ExPetr, PetrWrap, Petya или NotPetya. В отличие от оригинального Petya, авторы Diskcoder.C изменили код MBR таким образом, что восстановить данные стало невозможно. Точнее, атакующие не могут отправить жертве ключ расшифровки, и его невозможно ввести в соответствующее поле, поскольку он содержит недопустимые символы.

Визуально MBR часть Diskcoder.C выглядит как слегка модифицированная версия Petya: сначала она показывает сообщение, в котором выдает себя за CHKDSK – утилиту проверки диска от Microsoft. В процессе фейкового сканирования Diskcoder.C на самом деле шифрует данные.


Рисунок 8. Фейковое сообщение CHKDSK, отображаемое Diskcoder.C.

Когда шифрование завершено, код MBR отображает следующее сообщение с инструкциями для оплаты, но, как уже было доказано, эта информация бесполезна.


Рисунок 9. Сообщение Diskcoder.C с инструкциями для оплаты выкупа.

Остальной код, помимо заимствованного MBR, написан авторами вредоносной программы. Он включает шифратор файлов, который может использоваться в дополнение к шифрующей диск MBR. Вредоносная программа использует алгоритмы AES-128 и RSA-2048.

Стоит отметить, что авторы допустили ошибки, что сократило возможности дешифровки файлов. Например, Diskcoder.C шифрует только первый 1 Мб данных и не записывает header и footer, только исходные зашифрованные данные. Малварь не переименовывает файлы, поэтому сложно сказать, какие файлы зашифрованы, а какие нет.

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


Рисунок 10. Список целевых расширений Diskcoder.C.

После выполнения Diskcoder.C пытается увеличить охват при помощи эксплойта EternalBlue, который использует бэкдор DoublePulsar, работающий в режиме ядра. Точно такой же метод использовался в вымогателе WannaCryptor.D.

Diskcoder.C также использовал метод, позаимствованный у Win32/Filecoder.AESNI.C (XData) – он использует упрощенную версию Mimikatz, чтобы получить учетные данные, а затем исполняет вредоносное ПО на других машинах локальной сети с помощью SysInternals PsExec.

Наконец, авторы Diskcoder.C использовали третий метод распространения – механизм WMI.

Все три метода применялись для распространения Diskcoder.C внутри сетей. В отличие от WannaCryptor, новый шифратор использовал эксплойт EternalBlue только на компьютерах в диапазоне адресов локальной сети.

Почему эпидемия вышла за пределы Украины? Наше исследование показало, что зараженные компании в других странах подключались через VPN к своим украинским филиалам или бизнес-партнерам.

Начальный вектор заражения


И Diskcoder.C, и Win32/Filecoder.AESNI.C использовали атаки на цепь поставок (supply-chain attack) в качестве начального вектора заражения. Эти семейства вредоносного ПО передавались при помощи программного обеспечения для отчетности и документооборота M.E.Doc, которое широко используется в бухгалтерском учете.

Существует несколько вариантов проведения этих атак. У M.E.Doc есть внутренняя система обмена документами и сообщениями, так что хакеры могли использовать фишинг. В этом случае необходимо взаимодействие с пользователем, возможно, не обошлось без социальной инженерии. Поскольку Win32/Filecoder.AESNI.C не распространился слишком широко, мы сначала решили, что были задействованы именно эти методы.

Но последующая эпидемия Diskcoder.C дает основания предполагать, что у хакеров был доступ к серверу обновлений легитимного ПО M.E.Doc. С его помощью атакующие могли направлять вредоносные обновления с их установкой автоматически без участия пользователя. Поэтому так много систем на Украине пострадало от этой атаки. Кажется, что создатели малвари недооценили способности Diskcoder.C к экспансии.

Исследователи ESET нашли подтверждение этой теории. Мы обнаружили PHP-бэкдор в файле medoc_online.php в одной из директорий на сервере FTP M.E.Doc. Доступ к бэкдору можно было получить через HTTP, хотя он был зашифрован, и атакующему нужен был пароль для его использования.


Рисунок 11. Директория с РНР-бэкдором на FTP.

Надо сказать, что есть признаки, указывающие на то, что Diskcoder.C и Win32/Filecoder.AESNI.C – не единственные семейства вредоносных программ, которые использовали этот вектор. Можем предположить, что вредоносные обновления были применены для скрытого проникновения в компьютерные сети, принадлежащие приоритетным объектам.

Одной из вредоносных программ, распространявшихся с помощью скомпрометированного механизма обновлений M.E.Doc, был VBS-бэкдор, который использует группа TeleBots. На этот раз атакующие снова использовали доменные имена, связанные с финансовой темой: bankstat.kiev[.]ua.

В день начала эпидемии Diskcoder.C А-запись этого домена была изменена на 10.0.0.1.

Выводы


Группа TeleBots совершенствует инструменты деструктивных атак. Вместо направленных фишинговых писем с документами, содержащими вредоносные макросы, они использовали более сложную схему, известную как кибератаки на цепи поставок (supply-chain attack). До начала эпидемии группа атаковала преимущественно финансовый сектор. Вероятно, что последняя кампания была нацелена на украинский бизнес, но атакующие недооценили возможности вредоносной программы – малварь вышла из-под контроля.

Индикаторы заражения (IoC)


Детектирование продуктами ESET:
Win32/TeleBot trojan
VBS/Agent.BB trojan
VBS/Agent.BD trojan
VBS/Agent.BE trojan
Win32/PSW.Agent.ODE trojan
Win64/PSW.Agent.K trojan
Python/Filecoder.R trojan
Win32/Filecoder.AESNI.C trojan
Win32/Filecoder.NKH trojan
Win32/Diskcoder.C trojan
Win64/Riskware.Mimikatz application
Win32/RiskWare.Mimikatz application


C&C:
transfinance.com[.]ua (IP: 130.185.250.171)
bankstat.kiev[.]ua (IP: 82.221.128.27)
www.capital-investing.com[.]ua (IP: 82.221.131.52)


Легитимные серверы, используемые авторами вредоносной программы:
api.telegram.org (IP: 149.154.167.200, 149.154.167.197, 149.154.167.198, 149.154.167.199)

VBS-бэкдор:
1557E59985FAAB8EE3630641378D232541A8F6F9
31098779CE95235FED873FF32BB547FFF02AC2F5
CF7B558726527551CDD94D71F7F21E2757ECD109


Mimikatz:
91D955D6AC6264FBD4324DB2202F68D097DEB241
DCF47141069AECF6291746D4CDF10A6482F2EE2B
4CEA7E552C82FA986A8D99F9DF0EA04802C5AB5D
4134AE8F447659B465B294C131842009173A786B
698474A332580464D04162E6A75B89DE030AA768
00141A5F0B269CE182B7C4AC06C10DEA93C91664
271023936A084F52FEC50130755A41CD17D6B3B1
D7FB7927E19E483CD0F58A8AD4277686B2669831
56C03D8E43F50568741704AEE482704A4F5005AD
38E2855E11E353CEDF9A8A4F2F2747F1C5C07FCF
4EAAC7CFBAADE00BB526E6B52C43A45AA13FD82B
F4068E3528D7232CCC016975C89937B3C54AD0D1


Win32/TeleBot:
A4F2FF043693828A46321CCB11C5513F73444E34
5251EDD77D46511100FEF7EBAE10F633C1C5FC53


Win32/PSW.Agent.ODE (CredRaptor):
759DCDDDA26CF2CC61628611CF14CFABE4C27423
77C1C31AD4B9EBF5DB77CC8B9FE9782350294D70
EAEDC201D83328AF6A77AF3B1E7C4CAC65C05A88
EE275908790F63AFCD58E6963DC255A54FD7512A
EE9DC32621F52EDC857394E4F509C7D2559DA26B
FC68089D1A7DFB2EB4644576810068F7F451D5AA


Win32/Filecoder.NKH:
1C69F2F7DEE471B1369BF2036B94FDC8E4EDA03E

Python/Filecoder.R:
AF07AB5950D35424B1ECCC3DD0EEBC05AE7DDB5E

Win32/Filecoder.AESNI.C:
BDD2ECF290406B8A09EB01016C7658A283C407C3
9C694094BCBEB6E87CD8DD03B80B48AC1041ADC9
D2C8D76B1B97AE4CB57D0D8BE739586F82043DBD


Win32/Diskcoder.C:
34F917AABA5684FBE56D3C57D48EF2A1AA7CF06D

PHP shell:
D297281C2BF03CE2DE2359F0CE68F16317BF0A86
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332058/


Метки:  

Symfony: Webpack Encore — плагин для управления ресурсами

Суббота, 01 Июля 2017 г. 03:01 + в цитатник
Позавчера обнаружил новость о том, что команда Symfony выпустила плагин Webpack Encore для интеграции замечательного инструмента Webpack в ваше приложение. Если вы не знакомы с Webpack, то я настоятельно рекомендую ознакомиться с ним, так как он возможно решит множество вопросов связанных с управлением ресурсами в вашем проекте. В любом случае даже если вы не собираетесь его использовать, знать о том что он существует будет крайне полезным. Очень хорошо этот инструмент описан тут.

Вступление


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

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

Необходимые инструменты


Вам потребуется какой-либо тестовый проект на Symfony >= 3.3, NodeJS и менеджер пакетов Yarn. Вы можете использовать Npm, но в данном посте примеры будут с использованием Yarn.

Для теста мы будем подключать к проекту FontAwesome, Jquery, Bootstrap и какие-то свои выдуманные ресурсы.

Установка


Сначала установим плагин Webpack Encore. В корне приложения:

yarn add @symfony/webpack-encore --dev


Мы будем использовать SASS, поэтому добавим пару пакетов:

yarn add sass-loader node-sass --dev


Не забудьте добавить в .gitignore каталог node_modules.

Подготовка ресурсов


Я создам в корне приложения каталог assets/ куда положу все необходимые ресурсы. В результате мой каталог будет выглядеть так:

+-assets/
---+ dist/
------+ fontawesome/
------+ jquery/
------+ bootstrap/


Дополнительно я создам в корне assets/ файл app.scss, который будет главным файлом ресурсов. Вовсе не обязательно иметь каталог dist с библиотеками, их можно установить с помощью Yarn. Я выбрал такой путь для большей наглядности.

Теперь необходимо создать инструкции для плагина. Для этого в корне приложения создадим файл webpack.config.js со следующим содержимым:

/* подключим плагин */
var Encore = require('@symfony/webpack-encore');

Encore
   /* Установим путь куда будет осуществляться сборка */
   .setOutputPath('web/build/')
   /* Укажем web путь до каталога web/build */
   .setPublicPath('/build')
   /* Каждый раз перед сборкой будем очищать каталог /build */
   .cleanupOutputBeforeBuild()
   /* Добавим наш главный файл ресурсов в сборку */
   .addStyleEntry('styles', './assets/app.scss')
   /* Включим поддержку sass/scss файлов */
   .enableSassLoader()
   /* В режиме разработки будем генерировать карту ресурсов */
   .enableSourceMaps(!Encore.isProduction());

/* Экспортируем финальную конфигурацию */
module.exports = Encore.getWebpackConfig();


Теперь можно заняться ресурсами. Отредактируем наш файл app.scss:

@import "dist/fontawesome/css/font-awesome";
@import "dist/bootstrap/css/bootstrap";
/* Тут можно определить свои стили или подключить собственные библиотеки */


Запускаем билд. В корне приложения:

./node_modules/.bin/encore dev


Если все прошло гладко, вы увидите в каталоге web/build файл styles.css, а так же папку fonts, куда скопированы все шрифты font-awesome на которые ссылается font-awesome.css. Если вы прописали какие-то свои стили, которые используют изображения, то эти изображения так же подтянутся в папку web/build/images. В результирующих файлах стилей все пути соответственно будут переписаны.

Благодаря сгенерированным картам ресурсов, мы можем комфортно использовать отладчик в браузере. Помимо стилей, в каталоге web/builds появится файл manifest.json, о нем чуть позже.

Сейчас необходимо подключить JavaScript, который нам необходим. Для этого добавим в каталог assets/ файл app.js со следующим содержимым:

var $ = require('./dist/jquery/jquery-3.2.1');
require('./dist/bootstrap/js/bootstrap');


Теперь отредактируем немного наш файл webpack.config.js:

/* подключим плагин */
var Encore = require('@symfony/webpack-encore');

Encore
   /* Установим путь куда будет осуществляться сборка */
   .setOutputPath('web/build/')
   /* Укажем web путь до каталога web/build */
   .setPublicPath('/build')
   /* Каждый раз перед сборкой будем очищать каталог /build */
   .cleanupOutputBeforeBuild()

   /* --- Добавим основной JavaScript в сборку --- */
   .addEntry('scripts', './assets/app.js')

   /* Добавим наш главный файл ресурсов в сборку */
   .addStyleEntry('styles', './assets/app.scss')
   /* Включим поддержку sass/scss файлов */
   .enableSassLoader()
   /* В режиме разработки будем генерировать карту ресурсов */
   .enableSourceMaps(!Encore.isProduction());

/* Экспортируем финальную конфигурацию */
module.exports = Encore.getWebpackConfig();


Теперь перезапустим сборку:

./node_modules/.bin/encore dev


Если все прошло гладко, вы получите файл scripts.js в каталоге web/builds.
У вас может возникнуть проблема со скриптами, которые ожидают, что JQuery будет доступен глобально. Когда вы делаете var $ = require(some.js), то просто подключаете скрипт в текущий контекст, а не глобально. Поэтому скрипты, которые вы определяете в шаблонах, а так же некоторые другие библиотеки, ожидающие глобального JQuery работать не будут.

Есть несколько вариантов решения проблемы. Для всех пакетов, которые вы подключаете через require, обеспечить доступ к $ или JQuery, можно добавив в файл webpack.config.js такую инструкцию:

/* подключим плагин */
var Encore = require('@symfony/webpack-encore');

Encore
   /* Установим путь куда будет осуществляться сборка */
   .setOutputPath('web/build/')
   /* ... */
  
   .autoProvidejQuery()

   /* В режиме разработки будем генерировать карту ресурсов */
   .enableSourceMaps(!Encore.isProduction());

/* Экспортируем финальную конфигурацию */
module.exports = Encore.getWebpackConfig();


Теперь все в порядке, однако скрипты, которые вы определяете в шаблонах по прежнему не будут видеть JQuery. В таком случае вероятно вам понадобится вручную добавить библиотеку в глобальную область видимости. Для этого отредактируем файл app.js:

var $ = require('./dist/jquery/jquery-3.2.1');
global.$ = global.jQuery = $;
require('./dist/bootstrap/js/bootstrap');


Еще раз запустим сборку и теперь у нас все очень компактно упаковано, разложено по каталогам, мы имеем один файл стилей и один файл скриптов. Все зависимости (шрифты, картинки) скопированы в соответствующие места.

Включение версионирования:


/* webpack.config.js */
// ...
.enableVersioning()
// ...


Если вы включили данную функцию, то вам потребуется внести некоторые настройки в конфигурацию вашего Symfony проекта:

# app/config/config.yml
framework:
    # ...
    assets:
        # Функционал доступен начиная с Symfony 3.3
        json_manifest_path: '%kernel.project_dir%/web/build/manifest.json'


Файл manifest.json, хранит карту соответствия файлов ресурсов их версионным аналогам. Теперь подключая в вашем Twig шаблоне стиль:




на самом деле будет подключен файл вида: build/styles.c1a32e.css

Подключение стилей через JavaScript:


Вы вполне можете себе позволить импортировать файлы стилей через JavaScript. На примере нашего проекта можно было бы сделать так в app.js:

require('./app.scss');

var $ = require('./dist/jquery/jquery-3.2.1');
global.$ = global.jQuery = $;
require('./dist/bootstrap/js/bootstrap');


В этом случае, в webpack.config.js не нужно добавлять addStyleEntry. После сборки автоматически будет создан js файл и одноименный css файл со всеми запрошенными стилями в данном скрипте.

Деплой



Достаточно холиварная тема, но я все-таки затрону ее. Если вы предпочитаете производить сборку на стороне сервера, тогда вам стоит добавить в .gitignore каталог web/build и на продакшн сервере выполнять:

./node_modules/.bin/encore production


В режиме production ваши скрипты и стили будут дополнительно минифицированы.

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

Заключение


Данный материал не претендует на использование в реальных условиях. В каждой конкретной ситуации будут свои подходы и настройки. Я лишь хотел показать еще один вариант управления ресурсами в проекте. Те кто знаком с webpack, вероятно найдут для себя полезной информацию о том, что появился такой плагин от разработчиков Symfony. Кто не знаком с webpack возможно заинтересуются этим инструментом.

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

Официальный сайт webpack: https://webpack.js.org/
Документация к плагину: http://symfony.com/doc/current/frontend.html

Спасибо за внимание.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332052/


Метки:  

Экономия на спичках или восстановление данных из скрежещущего HDD Seagate ST3000NC002-1DY166

Пятница, 30 Июня 2017 г. 23:00 + в цитатник
Медицинские учреждения с государственной формой собственности постоянно испытывают недостаток финансирования из бюджета. Поэтому план закупок дорогостоящего медицинского оборудования составляется таким образом, чтобы исключить все возможные дополнительные расходы. Но в погоне за экономией из вида упускаются вполне очевидные любому системному администратору вещи.

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


рис. 1

Собственных средств больницы хватило лишь на приобретение ПК для врача-рентгенолога с единственным жестким диском Seagate Constellation CS ST3000NC002-1DY166 емкостью 3 Тб. Первоначально планировалось это использовать как временное решение, а далее, когда появятся «свободные деньги», реализовать планы по созданию системы резервного копирования.

Но планам не суждено было сбыться. Денег больнице по-прежнему катастрофически не хватало, и была масса более приоритетных задач. В итоге, временное решение стало постоянным. Шли дни, на жестком диске накапливались данные пациентов, причем в некоторых случаях от этих данных могла зависеть человеческая жизнь.

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

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

С такой симптоматикой накопитель был доставлен в нашу лабораторию восстановления данных. Seagate Constellation CS ST3000NC002-1DY166 семейства Grenada. Жесткие диски этого семейства продавались весьма массово, и в связи с высокой распространенностью повидать их смогли во многих лабораториях восстановления данных. Не только нами, но и многими другими лабораториями был зафиксирован факт, что диски данного семейства при выходе из строя нередко оказываются с сильно поврежденным БМГ и запиленными пластинами.


рис. 2

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

Причины подобного явления волновали многих. Выдвигалось множество версий, некоторые звучали достаточно убедительно. Например, одна из распространившихся версий, опубликованная на Habrahabr "Если Seagate запылился…", гласила, что основной причиной начала деградационных процессов является попадание пыли из-за неподходящего уплотнителя между контактной колодкой и корпусом БМГ.

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

Современные накопители способны управлять высотой полета слайдеров над поверхностью пластин посредством нагревательного элемента и Seagate Grenada не является исключением.


рис. 3 (рисунок заимствован из публичного документа)

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

На основании этого анализа также можно дать рекомендацию ни в коем случае не менять платы между накопителями данного семейства без переноса ПЗУ, так как попытки старта с чужой платой (с чужими адаптивными параметрами в ПЗУ) могут привести к контакту слайдеров с поверхностью пластин. Тем более в накопителях Seagate F3 архитектуры попытки старта с чужим ПЗУ обречены на провал.

Когда мы видим, что к нам поступает Seagate Grenada, который со слов клиента начал постукивать, то диагностические мероприятия начинаем с обеспыливания накопителя и вскрытия в ламинарном боксе. Снимаем БМГ, фильтр рециркуляции воздуха и тщательно обследуем под микроскопом.

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

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

Выполняем процедуру сбора накопителя с одним физически удаленным слайдером и подвеской. Так как при физическом отсутствии одной из головок накопитель не сможет пройти процедуру калибровки, то необходимо модифицировать карту головок в ПЗУ, чтобы обойти этап калибровки по головке №2. Для этого карту головок в ПЗУ 0, 1, 2, 3, 4, 5 меняем на 0, 1, 1,3, 4, 5. Перед тем, как записывать модифицированное ПЗУ, создадим несколько резервных копий оригинального с обязательным сравнением результатов чтения с предыдущим.

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


рис. 4

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

Читаем содержимое 0 сектора и обнаруживаем там чьё-то упущение. На 3 ТБ диск с эмуляцией сектора 512 байт использовалась классическая таблица разделов вместо положенной GPT. На диске присутствуют три раздела.


рис. 5 — Таблица разделов

Первый раздел NTFS (0x07) имеет статус активного и начинается с 0x00000800 (2048) сектора, размер 0x00032000 (204 800) секторов.

Второй раздел NTFS (0x07) начинается с 0x00032800 (206 848) сектора, размер 0x06175800 (102 193 152) секторов.

Третий раздел NTFS (0x07) начинается с 0x061A8000 (102 400 000) сектора, размер 0xF9E58000 (4 192 567 296) секторов.

Заглянем в сектор 0x100000000 (4 294 967 296). Он целиком заполнен нулями, признаки таблицы разделов или загрузочного сектора раздела отсутствуют.

Предварительно можно сделать вывод, что данный диск использовался только в границах первых 2ТБ, оставшиеся 794,52ГБ не использовались в процессе эксплуатации.

Создаем задачу посекторного копирования в Data Extractor, строим карту зонного распределения без учета зон головки №2, так как самой головки нет, а вместо нее обманка в карте.


рис. 6 — Карта мини зон

Процесс чтения идет без особых затруднений, и в течение нескольких проходов получаем начитанными 4 846 ххх ххх секторов их 5 860 533 160. Непрочитанными по головкам 0, 1, 3, 4, 5 после нескольких операций перечитки осталось 72 сектора (36Кб), и все они сосредоточены на поверхности, читаемой головкой №5. Учитывая, что чтение было стабильным, то были прочитаны все зоны после 4 294 967 296 сектора. Предположение о том, что диск частично не использовался, получило подтверждение.

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

Для полноценного чтения нам необходимо произвести пересадку донорского БМГ. Учитывая, что это Seagate Grenada, то выбор донора осуществляем среди аналогичных накопителей, принадлежащих этому семейству, с одинаковой ревизией коммутатора-предусилителя и близкими адаптивными параметрами. ST3000NC002, ST3000DM001 являются представителями одного семейства. Подобрав подходящий донор, выполним пересадку БМГ.


рис. 7

Записываем в винчестер-пациент ПЗУ с оригинальной картой головок обратно. При подаче питания слышим несколько неуверенное прохождение калибровочного теста, но, тем не менее, микропрограмма загружена, и система трансляции проинициализирована. Тестируем способность чтения головки №2 в зонах разной плотности. Удостоверившись, что чтение присутствует в разных зонах, приступаем к чтению пользовательских данных.

Просматриваем содержимое загрузочного сектора третьего раздела.


рис. 8 — Загрузочный сектор NTFS

По смещению 0x0B располагается WORD 0x0200, что означает, что размер сектора 512 байт.

По смещению 0x0D располагается BYTE 0x08, что означает, что в одном кластере 8 секторов, размер кластера вычисляется перемножением размера сектора на количество секторов в кластере, то есть 0x0200*0x08=0x1000 (4096) байт.

По смещению 0x30 располагается QWORD 0x00000000000C0000 (786 432) в нем указан номер первого кластера MFT.

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

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

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

На протяжении двух суток с постоянными изменениями сценариев чтения удается завершить вычитывание более, чем на 99,9% необходимых цепочек, и на этом положительная динамика останавливается, а на дефектных зонах появляется запил, подобный тому, что на рис. 2.


рис. 9 — Вид запила под микроскопом

К сожалению, иных вариантов чтения уже не было, это был последний шанс прочитать хоть что-то из поврежденных зон. При формировании отчета о файлах, расположенных в проблемных зонах, наблюдаем относительно неплохую картину: из более 198 000 файлов непрочитанными остаются чуть менее 2000. Но, отходя от сухих цифр, в которых рапортуется о 98,9% результате, приходит осознание того, что за этими чуть менее 2000 файлами стоит несколько сотен живых людей, чьи результаты посещения рентген-кабинета канули в небытие из-за мелочной экономии. Очень хочется верить, что утерянные результаты не были критически важными, и их потеря не отразилась на чьей-то жизни.

При выдаче данной информации была проведена консультация системного администратора, каким образом следить за состоянием жестких дисков. Накопители, подобные этому, не деградируют в одно мгновение. Если регулярно проверять показания SMART, контролировать хотя бы необходимый минимум атрибутов, а не ждать, когда накопитель начнет рапортовать «SMART status – BAD» на команду 0xB0 0xDA, то в большинстве случаев можно заметить надвигающуюся угрозу и своевременно предпринять меры. Разумеется, существуют иные проблемы, которые развиваются куда более стремительно, и регулярное наблюдение за показателями SMART ничем не поможет. Учитывая вероятность развития неблагоприятных событий, стоит хорошо продумать систему резервного копирования, причем контроль ее работы должен осуществляться не только единственным системным администратором.

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

Предыдущая публикация: Грех администратора или восстановление данных из стучащего HDD Western Digital WD5000AAKX
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332004/


Who is Mr. Hacker?

Пятница, 30 Июня 2017 г. 22:57 + в цитатник


Коллеги и друзья! Рад сообщить о запуске новой, 11-й по счету лаборатории тестирования на проникновение: Test Lab v. 11!

Лаборатория представляет собой копию реальной корпоративной сети компании, содержащую типичные уязвимости и ошибки конфигурации, а принять участие сможет каждый желающий. В настоящее время в лаборатории зарегистрировано более 17 000 человек!

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



В новой лаборатории содержатся следующие уязвимые ресурсы и сервисы:


  • веб-сайт компании;
  • почтовый сервис;
  • CRM-система;
  • AD;
  • АРМ на базе Win;
  • вспомогательные сервера и службы;
  • АРМ и сервисы технического персонала;
  • системы контроля доступа;
  • защитные средства.


Заложенные вектора атак:


  • OWASP TOP-10;
  • MiTM;
  • Public vulns;
  • Security policy vulns;
  • Auth bypass;
  • Privilege escalation;
  • обход защитных средств;
  • misconfiguration и другое.


Сценарий атаки:


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

Дерзайте!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332062/


Метки:  

Security Week 26: ExPetr – не вымогатель, Intel PT позволяет обойти PatchGuard, в Malware Protection Engine снова RCE

Пятница, 30 Июня 2017 г. 19:45 + в цитатник
Мимикрия чрезвычайно распространена в животном мире. Чтобы было проще прятаться от хищников, или наоборот, легче подкрадываться к добыче незамеченными, звери, рептилии, птицы и насекомые приобретают окраску, схожую с окружающей местностью. Встречается мимикрия и под предметы, и, наконец, под животных других видов – более опасных, или менее вкусных.

Схожим образом поступает новый троянец-вымогатель ExPetr, который вроде бы Petya, да не совсем он. Распространяясь как чума, он навел шороху в 150 странах. Один из векторов распространения, но не единственный – сладкая парочка EternalBlue и DoublePulsar, от которой многие так и не удосужились запатчиться после WannaCry.

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



Старый добрый Petya генерил для каждой жертвы идентификатор, который надо было отправить вымогателям после уплаты выкупа. Именно из него мошенники вычисляли ключ, который возвращали жертве, чтобы она могла восстановить свои файлы. И ExPetr показывает жертве идентификатор, вот только заведомо бесполезный – просто рандомный набор цифр. Никто ничего расшифровывать не собирался изначально.

Тут возникает резонный вопрос, зачем было городить огород с требованием выкупа и маскироваться под относительно мирного Petya? Возможно, это не особо хитрая попытка скрыть, что за новой эпидемией стоят не простые парни с раена, пытающиеся подзаработать на халявном эксплойте, а кто-то гораздо более злобный, и не нуждающийся в деньгах. Правда, кое-какие деньги авторы ExPetr все-таки заработали.

Разработан способ обхода защиты ядра Windows 10

Новость. Исследование. Как известно, Windows 10 обзавелась рядом хороших, годных защитных функций. Одна из них, это PatchGuard, извела руткиты практически на корню. Но ребята из CyberArk утверждают, что ждать осталось недолго, так как они уже придумали способ беспалевного запуска кода в пространстве ядра Windows 10.

Проблема кроется в замечательной технологии Intel PT (Processor Trace), которая позволяет секьюрити-продуктам отслеживать поток команд, исполняемый процессором, чтобы моментально определять возможную атаку. Задумка отличная, но CyberArk смогли использовать PT для запуска своего кода в пространстве ядра. Они, конечно, отрепортили все это в Microsoft, но получили ответ, что все это ерунда, так как для использования уязвимости нужны админские права. А раз так, то это уже и не уязвимость, ибо админу и так позволено на машине все, и если таковым стал злоумышленник – туши свет, сливай воду.

И все же это не совсем так. Даже троянца с админскими правами можно задетектить и уничтожить. Но эксплойт от CyberArk позволяет исполнять код абсолютно незаметно для любых защитных систем. Вполне возможно, что нечто подобное какие-то плохие парни давно используют, только поймать их на этом будет очень и очень сложно.

В Malware Protection Engine снова нашли RCE

Новость. Исследователям из Google Project Zero, похоже, понравилось препарировать защитный движок от Microsoft. Уже третий раз за последние месяцы в нем найдена уязвимость, позволяющая удаленно запускать произвольный код.

Проблема кроется в x86-эмуляторе, на котором MsMpEng прогоняет недоверенные PE-файлы. Оказалось, что разработчики Microsoft оставили торчать наружу один из вызовов API эмулятора, причем не случайно, а «по ряду причин». Хитрый Тевис Орманди из Project Zero смог использовать это в хитросварганенном файле, при сканировании которого в эмуляторе возникает повреждение памяти, что, согласно Microsoft, позволяет «запускать код, устанавливать программы, изменять данные, создавать новые учетные записи».

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

Напомню, что дырявый MsMpEng содержится в Microsoft Endpoint Protection, Microsoft Forefront Endpoint Protection, Windows Defender и Microsoft Intune Endpoint Protection, однако уязвимость присутствует лишь в 32-битной версии движка.

Древности


«Astra-498, -510, -521»

Резидентные неопасные вирусы. Поражают SYS-файлы текущего каталога при каждом вызове функции DOS FindFirst. Записываются в конец файлов, у которых изменяют только адрес подпрограммы прерывания (interrupt). Свою TSR-копию записывают в таблицу векторов по адресам 0020:xxxx. Перехватывают int21h.

Содержат текст «(5)» и, в зависимости от версии вируса, одну из строк: «© AsTrA, 1990, JPN», «(С) AsTrA, 1990», «© AsTrA, JPN». Выводят на экран фразы «I like cold flavor !», «I like fragrant smell of flower!» или «I like a flower’s smell!».

Цитата по книге «Компьютерные вирусы в MS-DOS» Евгения Касперского. 1992 год. Страницa 26.

Disclaimer: Данная колонка отражает лишь частное мнение ее автора. Оно может совпадать с позицией компании «Лаборатория Касперского», а может и не совпадать. Тут уж как повезет.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332050/


Метки:  

Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1032 1031 [1030] 1029 1028 ..
.. 1 Календарь