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

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

От автора: вы хотите писать хороший код, но не знаете, с чего начать, пробуете читать то и это, реализуете на практике прочитанное. Но все равно, вопросов остается больше, чем ответов. Нужно ли убирать «мертвый» код? Что делать, если обнаружена неиспользуемая переменная в уже написанном проекте? Как найти проблемные паттерны и все исправить? Эти вопросы важны, и многие из нас пытаются на них ответить. Но лучше всего — все делать хорошо уже с нуля, так, чтобы потом не приходилось искать проблемные места и латать дыры, теряя время. Для создания хорошего кода есть несколько инструментов, которые можно назвать незаменимыми.

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

Skillbox рекомендует: Практический курс «Профессия веб-разработчик».

Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».

Весь список инструментов статьи вот:

  • Prettier
  • ESLint
  • Automate Format and Lint on Save
  • Husky
  • Lint-staged
  • With Husky and Lint-staged Combined
  • EditorConfig

Начнем с Prettier

Этот инструмент — продуманный оптимизатор кода.

Зачем он нужен?

Подчищает уже готовый код. Только представьте, что вам нужно оптимизировать около 20 тысяч строк. Prettier сделает все это автоматически и быстро.

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

Если вы начинающий программист, который хочет писать красивый код, но не знаете, с чего начать, попробуйте Prettier.

Установка

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

npm init -y

Эта команда позволит создать файл package.json.

Далее разбираемся с зависимостями.

yarn add --dev prettier

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

{   "name": "react-boiler-plate",   "version": "1.0.0",   "description": "A react boiler plate",   "main": "src/index.js",   "author": "Adeel Imran",   "license": "MIT",   "scripts": {     "prettier": "prettier --write src/**/*.js"   },   "devDependencies": {     "prettier": "^1.14.3"   } }

Далее создаем src/ папку внутри папки app. И внутри src/ файл index.js. Назвать его вообще-то можно как угодно, главное — вставить в его тело вот это:

let person =                     {   name: "Yoda",                 designation: 'Jedi Master '                 };                   function trainJedi (jediWarrion) { if (jediWarrion.name === 'Yoda') {   console.log('No need! already trained'); } console.log(`Training ${jediWarrion.name} complete`)               }   trainJedi(person)               trainJedi({ name: 'Adeel',               designation: 'padawan' });

Теперь у нас есть src/app/index.js с корявым кодом.

Над ним можно выполнить вот такие операции:
— форматировать вручную;
— использовать автоматизацию;
— ничего не делать (Let things go and move on).

Третью опцию лучше не выбирать, иначе зачем нам вообще инструменты по оптимизации кода? Давайте выберем второй вариант. У нас есть зависимость и скрипт Prettier внутри нашего файла package.json.

Теперь создадим prettier.config.js в папке app.

module.exports = {   printWidth: 100,   singleQuote: true,   trailingComma: 'all',   bracketSpacing: true,   jsxBracketSameLine: false,   tabWidth: 2,   semi: true, };

printWidth позволит убедиться, что в коде не больше 100 символов;
singleQuote преобразует все двойные кавычки в одинарные;
trailingComma проверит наличие всех висячих запятых в коде, особенно в конце последнего свойства объекта. Объясняется это здесь
bracketSpacing управляет пробелами в объектных литералах:

If bracketSpacing is true - Example: { foo: bar } If bracketSpacing is false - Example: {foo: bar}   jsxBracketSameLine работает с форматированием JSX-элемента ">"     // true example   <button   className="prettier-class"   id="prettier-id"   onClick={this.handleClick}>   Click Here </button>   // false example   <button   className="prettier-class"   id="prettier-id"   onClick={this.handleClick} >   Click Here </button>

tabWidth определяет количество пробелов на уровне отступа.
semi — if true выводит; в конце стейтмента.
Вот полный список опций, с которыми может работать Prettier.

После того, как изначальная конфигурация готова, можно заняться скриптом.

“prettier”: “prettier — write src/**/*.js”

В примере выше скрипт ищет все .js-файлы в папке src/.
-write указывает на необходимость сохранения оптимизированных файлов с кодом.

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

yarn prettier

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

ESLint

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

Зачем он нужен в применении к JavaScript?

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

Чем ESLint выделяется среди себе подобных?

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

Сейчас наиболее актуальны два style guides:

  • Google JavaScript Style Guide
  • Airbnb JavaScript Style Guide

Что касается меня, то я рекомендую второй вариант. Он весьма популярен, в этом можно убедиться, зайдя в его GitHub.

Сначала давайте обновим наш package.json-файл:

{   "name": "react-boiler-plate",   "version": "1.0.0",   "description": "A react boiler plate",   "main": "src/index.js",   "author": "Adeel Imran",   "license": "MIT",   "scripts": {     "lint": "eslint --debug src/",     "lint:write": "eslint --debug src/ --fix",     "prettier": "prettier --write src/**/*.js"   },   "husky": {     "hooks": {       "pre-commit": "lint-staged"     }   },   "lint-staged": {     "*.(js|jsx)": ["npm run lint:write", "git add"]   },   "devDependencies": {     "babel-eslint": "^8.2.3",     "eslint": "^4.19.1",     "eslint-config-airbnb": "^17.0.0",     "eslint-config-jest-enzyme": "^6.0.2",     "eslint-plugin-babel": "^5.1.0",     "eslint-plugin-import": "^2.12.0",     "eslint-plugin-jest": "^21.18.0",     "eslint-plugin-jsx-a11y": "^6.0.3",     "eslint-plugin-prettier": "^2.6.0",     "eslint-plugin-react": "^7.9.1",     "husky": "^1.1.2",     "lint-staged": "^7.3.0",     "prettier": "^1.14.3"   } }

Что означает каждая опция:
eslint: это главный инструмент для работы с собственным кодом.
babel-eslint: пригодится, если вы работаете с Flow или экспериментальными функциями, которые еще не поддерживаются ESLint.
eslint-config-airbnb: этот пакет предоставляет разработчику конфигурацию Airbnb’s ESLint.
eslint-plugin-babel: плагин-компаньон для babel-eslint.
eslint-plugin-react: оптимизирует под react.
eslint-plugin-import: обеспечивает возможность работы с синтаксисом ES2015+ (ES6+) import/export.
eslint-plugin-prettier: оптимизирует взаимодействие ESLint с Prettier.

C базовыми вещами покончено, давайте начнем. Например, создадим файл .eslintrc.js в папке app/.

module.exports = { env: { es6: true, browser: true, node: true, }, extends: ['airbnb', 'plugin:jest/recommended', 'jest-enzyme'], plugins: [ 'babel', 'import', 'jsx-a11y', 'react', 'prettier', ], parser: 'babel-eslint', parserOptions: { ecmaVersion: 6, sourceType: 'module', ecmaFeatures: { jsx: true } }, rules: { 'linebreak-style': 'off', // Don't play nicely with Windows.   'arrow-parens': 'off', // Incompatible with prettier 'object-curly-newline': 'off', // Incompatible with prettier 'no-mixed-operators': 'off', // Incompatible with prettier 'arrow-body-style': 'off', // Not our taste? 'function-paren-newline': 'off', // Incompatible with prettier 'no-plusplus': 'off', 'space-before-function-paren': 0, // Incompatible with prettier   'max-len': ['error', 100, 2, { ignoreUrls: true, }], // airbnb is allowing some edge cases 'no-console': 'error', // airbnb is using warn 'no-alert': 'error', // airbnb is using warn   'no-param-reassign': 'off', // Not our taste? "radix": "off", // parseInt, parseFloat radix turned off. Not my taste.   'react/require-default-props': 'off', // airbnb use error 'react/forbid-prop-types': 'off', // airbnb use error 'react/jsx-filename-extension': ['error', { extensions: ['.js'] }], // airbnb is using .jsx   'prefer-destructuring': 'off',   'react/no-find-dom-node': 'off', // I don't know 'react/no-did-mount-set-state': 'off', 'react/no-unused-prop-types': 'off', // Is still buggy 'react/jsx-one-expression-per-line': 'off',   "jsx-a11y/anchor-is-valid": ["error", { "components": ["Link"], "specialLink": ["to"] }], "jsx-a11y/label-has-for": [2, { "required": { "every": ["id"] } }], // for nested label htmlFor error   'prettier/prettier': ['error'], }, };

Добавляем файл .eslintignore в папку app/.

/.git
/.vscode
node_modules

Что делает файл .eslintrc.js?

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

module.exports = {    env:{},    extends: {},    plugin: {},    parser: {},    parserOptions: {},    rules: {}, };

env: среда определяет глобальные переменные, которые уже предопределены. Доступные среды в нашем случае — es6, браузер и нода. Es6 сделает доступными функции ECMAScript 6 кроме модулей. Browser добавит все глобальные переменные, такие как Windows. Соответственно node добавит все глобальные переменные Node.
extends: массив строк — каждая дополнительная конфигурация расширяет предыдущие. Прямо сейчас мы используем linting-правила с airbnb, которые расширяются на на jest, а затем на jest-enzyme.
Plugins: это базовые linting-правила, которые мы хотим использовать. Мы работаем с babel, import, jsx-a11y, react, prettier и всем, что я указал выше.
parser: по умолчанию ESLint использует Espree, но поскольку мы работаем с babel, то нужно использовать Babel-ESLint.
parserOptions: когда мы изменяем дефолтный парсер для Espree на babel-eslint, нам необходимо уточнить parserOptions .
rules: любые правила мы можем изменять или замещать здесь.

Если все ясно, давайте поговорим о .eslintignore. Эта опция помогает указывать все пути, которые нет необходимости обрабатывать при помощи ESLint. Я использую всего три таких пути:
/.git — когда не хочу затрагивать свои git-файлы
/.vscode, поскольку работаю с VS Code, а у этого редактора есть собственная конфигурация, которую необходимо уточнять для каждого проекта и я не хочу в нее лезть здесь.
node_modules — зависимости я тоже не трогаю, поэтому добавил их в список.

С этим все, давайте поговорим о только что добавленных скриптах для нашего package.json

«lint»: «eslint —debug src/»
«lint:write»: «eslint —debug src/ —fix»

$ yarn lint — запуская эту команду, вы проверяете все ваши файлы в src/, в итоге получаете подробный журнал с описанием проблемных мест в каждом файле, где будут найдены ошибки, которые затем сможете вручную запустить и исправить.

$ yarn lint:write — эта команда делает примерно то же самое, что и предыдущая. Единственное отличие в том, что здесь у yarn уже есть право записи — команда исправляет ошибки, удаляя их из кода.

Так, ну если вы продержались до этого момента, то честь вам и хвала.

Husky

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

Все, что нужно — просто установить Husky:

yarn add —dev husky

Далее добавляем в файл package.json сниппет:

"husky": {        "hooks": {            "pre-commit": "YOUR_COMMAND_HERE",      "pre-push": "YOUR_COMMAND_HERE"      }  },

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

Lint-staged

Помогает предотвратить попадание плохого кода в вашу git-ветку.

Почему Lint-staged?

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

Все, что нужно сделать, — установить проект:

yarn add —dev lint-staged

Далее в package.json-файл добавить вот это:

"lint-staged": {        "*.(js|jsx)": ["npm run lint:write", "git add"]  },

Так вы запустите lint: write, добавляя ее затем в область стейджа. Команда работает для файлов .js & .jsx, но вы можете сделать то же самое и для других файлов, если хотите.

Объединяем Husky и Lint-staged

Каждый раз, когда вы коммитите ваш код, запускается скрипт, который называется lint-staged. Он инициирует выполнение npm run lint:write, что позволит проверить и отформатировать код. Затем уже проверенный код попадает в область стейджа и выполняется коммит.

Финальный файл package.json должен выглядеть следующим образом:

{   "name": "react-boiler-plate",   "version": "1.0.0",   "description": "A react boiler plate",   "main": "src/index.js",   "author": "Adeel Imran",   "license": "MIT",   "scripts": {     "lint": "eslint --debug src/",     "lint:write": "eslint --debug src/ --fix",     "prettier": "prettier --write src/**/*.js"   },   "husky": {     "hooks": {       "pre-commit": "lint-staged"     }   },   "lint-staged": {     "*.(js|jsx)": ["npm run lint:write", "git add"]   },   "devDependencies": {     "babel-eslint": "^8.2.3",     "eslint": "^4.19.1",     "eslint-config-airbnb": "^17.0.0",     "eslint-config-jest-enzyme": "^6.0.2",     "eslint-plugin-babel": "^5.1.0",     "eslint-plugin-import": "^2.12.0",     "eslint-plugin-jest": "^21.18.0",     "eslint-plugin-jsx-a11y": "^6.0.3",     "eslint-plugin-prettier": "^2.6.0",     "eslint-plugin-react": "^7.9.1",     "husky": "^1.1.2",     "lint-staged": "^7.3.0",     "prettier": "^1.14.3"   } }

Теперь каждый раз, когда вы будете выполнять это
$ git add.
$ git commit -m «some descriptive message here»

код будет форматироваться в автоматическом режиме на основе правил из файла .eslintrc.js.

Поговорим об EditorConfig

Сначала создадим файл .editorconfig в директории app/. В него вставим следующий код:

# EditorConfig is awesome: EditorConfig.org

# top-most EditorConfig file
root = true

[*.md]
trim_trailing_whitespace = false

[*.js]
trim_trailing_whitespace = true

# Unix-style newlines with a newline ending every file
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
insert_final_newline = true
max_line_length = 100

Вот список редакторов, которые поддерживают работу EditorCondig. В список входит следующее — Web storm, App code, Atom, eclipse, emacs, bbedit.

Код выше делает вот что:

  • Вырезает пробелы из файлов .md и .js.
  • Задает стиль отступов вместо пробелов.
  • Устанавливает размер отступа до 2.
  • Приводит конец строки к единому стандарту.
  • Добавляет новую строку в конец файла.
  • Устанавливает длину строки в 100 символов.

Собственно, теперь все готово. Если вам нужен исходный код, вот он.

Skillbox рекомендует:


ссылка на оригинал статьи https://habr.com/company/skillbox/blog/428231/

Машинное обучение: прогнозируем цены акций на фондовом рынке

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

Машинное и глубокое обучение стали новой эффективной стратегией, которую для увеличения доходов используют многие инвестиционные фонды. В статье я объясню, как нейронные сети помогают спрогнозировать ситуацию на фондовом рынке — например, цену на акции (или индекс). В основе текста мой проект, написанный на языке Python. Полный код и гайд по программе можно найти на GitHub. Другие статьи по теме читайте в блоге на Medium.

Нейронные сети в экономике

Изменения в сфере финансов происходят нелинейно, и иногда может показаться, что цены на акции формируются совершенно случайным образом. Традиционные методы временных рядов, такие как модели ARIMA и GARCH эффективны, когда ряд является стационарным — его основные свойства со временем не изменяются. А для этого требуется, чтобы ряд был предварительно обработан с помощью log returns или приведён к стационарности по-другому. Однако главная проблема возникает при реализации этих моделей в реальной торговой системе, так как при добавлении новых данных стационарность не гарантируется.

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

Обычно data science проект состоит из следующих операций:

  1. Сбор данных — обеспечивает набор необходимых свойств.
  2. Предварительная обработка данных — часто пугающий, но необходимый шаг перед использованием данных.
  3. Разработка и реализация модели — выбор типа нейронной сети и её параметров.
  4. Модели бэктестинга (тестирование на исторических данных) — ключевой шаг любой торговой стратегии.
  5. Оптимизация — поиск подходящих параметров.

Входные данные для нашей нейронной сети — данные о ценах на акции за последние 10 дней. С их помощью мы спрогнозируем цены на следующий день.

Сбор данных

К счастью, необходимые для этого проекта данные можно найти на Yahoo Finance. Данные можно собрать, используя их Python API pdr.get_yahoo_data(ticker, start_date, end_date) или напрямую с сайта.

Предварительная обработка данных

В нашем случае данные нужно разбить на обучающие наборы, состоящие из 10-ти прошлых цен и цены следующего дня. Для этого я определил класс Preprocessing, который будет работать с обучающими и тестовыми данными. Внутри класса я определил метод get_train(self, seq_len), который преобразовывает обучающие входные и выходные данные в NumPy массивы, задавая определенную длину окна (в нашем случае 10). Весь код выглядит так:

def gen_train(self, seq_len):    """    Generates training data    :param seq_len: length of window    :return: X_train and Y_train    """    for i in range((len(self.stock_train)//seq_len)*seq_len - seq_len - 1):        x = np.array(self.stock_train.iloc[i: i + seq_len, 1])        y = np.array([self.stock_train.iloc[i + seq_len + 1, 1]], np.float64)        self.input_train.append(x)        self.output_train.append(y)    self.X_train = np.array(self.input_train)    self.Y_train = np.array(self.output_train)

Аналогично я определил метод, который преобразовывает тестовые данные X_test и Y_test.

Модели нейронных сетей

Для проекта я использовал две модели нейронных сетей: Многослойный перцептрон Румельхарта (Multilayer Perceptron — MLP) и модель Долгой краткосрочной памяти (Long Short Term Model — LSTM). Кратко расскажу о том, как работают эти модели. Подробнее о MLP читайте в другой статье, а о работе LSTM — в материале Джейкоба Аунгиерса.

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

Для этого используется LSTM или Рекуррентные нейронные сети (Recurrent Neural Networks — RNN). RNN сохраняют определенную информацию о данных для последующего использования, это помогает нейронной сети анализировать сложную структуру связей между данными о ценах на акции. Но с RNN возникает проблема исчезающего градиента. Градиент уменьшается, потому что количество слоев повышается и уровень обучения (значение меньше единицы) умножается в несколько раз. Решают эту проблему LSTM, увеличивая эффективность.

Реализация модели

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

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

Начнем с более простой модели — MLP. В Keras строится последовательность и поверх неё добавляются плотные слои. Полный код выглядит так:

model = tf.keras.models.Sequential()  model.add(tf.keras.layers.Dense(100, activation=tf.nn.relu)) model.add(tf.keras.layers.Dense(100, activation=tf.nn.relu)) model.add(tf.keras.layers.Dense(1, activation=tf.nn.relu))  model.compile(optimizer="adam", loss="mean_squared_error")

С помощью Keras в пяти строках кода мы создали MLP со скрытыми слоями, по сто нейронов в каждом. А теперь немного об оптимизаторе. Популярность набирает метод Adam (adaptive moment estimation) — более эффективный оптимизационный алгоритм по сравнению с стохастическим градиентным спуском. Есть два других расширения стохастического градиентного спуска — на их фоне сразу видны преимущества Adam:

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

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

Adam объединяет в себе преимущества этих расширений, поэтому я выбрал его.

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

model.fit(X_train, Y_train, epochs=100)

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

model.evaluate(X_test, Y_test)

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

Для модели LSTM используется похожая процедура, поэтому я покажу код и немного объясню его:

model = tf.keras.Sequential() model.add(tf.keras.layers.LSTM(20, input_shape=(10, 1), return_sequences=True)) model.add(tf.keras.layers.LSTM(20)) model.add(tf.keras.layers.Dense(1, activation=tf.nn.relu))  model.compile(optimizer="adam", loss="mean_squared_error")  model.fit(X_train, Y_train, epochs=50)  model.evaluate(X_test, Y_test)

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

Модели бэктестинга

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

def back_test(strategy, seq_len, ticker, start_date, end_date, dim):    """    A simple back test for a given date period    :param strategy: the chosen strategy. Note to have already formed the model, and fitted with training data.    :param seq_len: length of the days used for prediction    :param ticker: company ticker    :param start_date: starting date    :type start_date: "YYYY-mm-dd"    :param end_date: ending date    :type end_date: "YYYY-mm-dd"    :param dim: dimension required for strategy: 3dim for LSTM and 2dim for MLP    :type dim: tuple    :return: Percentage errors array that gives the errors for every test in the given date range    """    data = pdr.get_data_yahoo(ticker, start_date, end_date)    stock_data = data["Adj Close"]    errors = []    for i in range((len(stock_data)//10)*10 - seq_len - 1):        x = np.array(stock_data.iloc[i: i + seq_len, 1]).reshape(dim) / 200        y = np.array(stock_data.iloc[i + seq_len + 1, 1]) / 200        predict = strategy.predict(x)        while predict == 0:            predict = strategy.predict(x)        error = (predict - y) / 100        errors.append(error)        total_error = np.array(errors)    print(f"Average error = {total_error.mean()}")

Однако, это упрощенная версия тестирования. Для полной системы бэктестинга нужно учитывать такие факторы, как «ошибка выжившего» (survivorship bias), тенденциозность (look ahead bias), изменение ситуации на рынке и транзакционные издержки. Так как это только образовательный проект, хватает и простого бэктестинга.

Прогноз моей модели LSTM на цены акций Apple в феврале

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

Оптимизация гиперпараметров

Для улучшения результатов модели после тестирования часто нужна оптимизация. Я не включил её в версию с открытым исходным кодом, чтобы читатели могли сами попробовать оптимизировать модель. Тем, кто не умеет оптимизировать, придется найти гиперпараметры, которые улучшат производительность модели. Есть несколько методов поиска гиперпараметров: от подбора параметров по сетке до стохастических методов.

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

Вывод

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

От редакции

Курсы «Нетологии» по теме:


ссылка на оригинал статьи https://habr.com/company/netologyru/blog/428227/

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

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

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

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

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

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

Почему возникла необходимости в такой библиотеке

image

В стеке sklearn, в пакетах xgboost, lightGBM были встроенные методы оценки важности фичей (feature importance):

  1. Gain
    Эта мера показывает относительный вклад каждой фичи в модель. для расчета мы идем по каждому дереву, смотрим в каждом узле дерева какая фича приводит к разбиению узла и насколько снижаетcя неопределенность модели согласно метрике (Gini impurity, information gain).
    Для каждой фичи суммируется её вклад по всем деревьям.
  2. Cover
    Показывает количество наблюдений для каждой фичи. Например, у вас 4 фичи, 3 дерева. Предположим, фича 1 в узлах дерева содержит 10, 5 и 2 наблюдения в деревьях 1, 2 и 3 соответственно Тогда для данной фичи важность будет равна 17 (10 + 5 + 2).
  3. Frequency
    Показывает, как часто данная фича встречается в узлах дерева, то есть считается суммарное количество разбиений дерева на узлы для каждой фичи в каждом дереве.

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

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

Есть этакий среднестатистический клиент банка «в вакууме». Как будут меняться предсказания модели в зависимости от изменения дохода?

Тут-то на помощь и приходит библиотека SHAP.

Рассчитываем важность фичей с помощью SHAP

В библиотеке SHAP для оценки важности фичей рассчитываются значения Шэпли (по имени американского математика и названа библиотека).

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

Немного предистории

image

Значения Шэпли идут из теории игр.

Рассмотрим сценарий: группа людей играет в карты. Как распределить призовой фонд между ними в соответствие с их вкладом?

Делается ряд допущений:

  • Сумма вознаграждения каждого игрока равна общей сумме призового фонда
  • Если два игрока сделали равный вклад в игру, они получают равную награду
  • Если игрок не внес никакого вклада, он не получает вознаграждения
  • Если игрок провел две игры, то его суммарное вознаграждение состоит из сумма вознаграждений за каждую из игр

Мы представляем фичи модели в качестве игроков, а призовой фонд — как итоговое предсказание модели.

Рассмотрим пример

Формула для расчета значения Шэпли для i-той фичи:

$$display$$\begin{equation*} \phi_{i}(p) =\sum_{S \subseteq N / \{i\}} \frac{|S|!(n — |S| -1)!}{n!}(p(S \cup \{ i \}) — p(S)) \end{equation*}$$display$$

Здесь:
$p(S \cup \{ i \})$ — это предсказание модели с i-той фичей,
$p(S)$ — это предсказание модели без i-той фичи,
$n$ — количество фичей,
$S$ — произвольный набор фичей без i-той фичи

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

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

Возьмём ванильный пример из документации xgboost.

image

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

В этом примере для простоты у нас есть две фичи: age (возраст) и gender (пол). Gender (пол) принимает значения 0 и 1.

Возьмём Bobby и посчитаем значение Шэпли для фичи age (возраст).

У нас есть два набора фичей S:
$\{ \}$ — нет фичей,
$\{gender\}$ — есть только фича пол.

Ситуация, когда нет значений фичей

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

Будет считать в данному случае, что модель усредняет предсказания по веткам дерева, то есть предсказание без фичей будет $[(2+0.1)/2 + (-1)] / 2 = 0.025$.

Если же мы добавим знание возраста, то предсказание модели будет $(2+0.1)/2 = 1.05$.

В итоге значение Шэпли для случая отсутствия фичей:

$\frac{|S|!(n - |S| -1)!}{n!}(p(S \cup \{ i \}) - p(S)) = \frac{1(2-1)!}{2!}(1.025) = 0.5125$

Ситуация, когда знаем пол

Для Bobby для ${gender}$ предсказание без фичи возраст, только с фичей пол, равно $[(2+0.1)/2 + (-1)] / 2 = 0.025$. Если же мы знаем возраст, то предсказание — это самое левое дерево, то есть 2.

В итоге значение Шэпли для этого случая:

$$display$$\begin{equation*} \frac{|S|!(n — |S| -1)!}{n!}(p(S \cup \{ i \}) — p(S)) = \frac{1(2-1)!}{2!}(1.975) = 0.9875 \end{equation*}$$display$$

Суммируем

Итогое значение Шэпли для фичи age (возраст):

$$display$$\begin{equation*} \phi_{Age Bobby} = 0.9875 + 0.5125 = 1.5 \end{equation*}$$display$$

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

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

На одном из проектов я анализировал отток сотрудников из компании. В качестве модели использовался xgboost.

Код в python

import shap  shap_test = shap.TreeExplainer(best_model).shap_values(df) shap.summary_plot(shap_test, df,                       max_display=25, auto_size_plot=True) 

Получившийся график важности фичей:
image

Как его читать:

  • значения слева от центральной вертикальной линии — это negative класс (0), справа — positive (1)
  • чем толще линия на графике, тем больше таких точек наблюдения
  • чем краснее точки на графике, тем выше значения фичи в ней

Из графика можно сделать интересные выводы и проверить их адекватность:

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

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

Просто и удобно!

Можно объяснить предсказание для конкретного сотрудника:

image

Или посмотреть зависимость предсказаний от конкретной фичи в виде 2D графика:

image

Заключение

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

Главные преимущества:

  • удобные визуализация и интерпретация
  • честный расчет важности фичей
  • возможность оценить фичи для конкретной подвыборки данных (например, чем отличаются наши покупатели от других клиентов в выборке), делается простым фильтром датасета в pandas и его анализом в shap, буквально пара строчек кода


ссылка на оригинал статьи https://habr.com/post/428213/

Руководство по доведению «до кондиции» клона популярного китайского мини-роутера Hame A15, он же «unbranded A5-V11»

Если я видел дальше других, то потому, что стоял на плечах гигантов.
И.Ньютон

Думаю многим известен маленький китайский роутер Hame A15 (он, а точнее массовые его копии, с улучшенными по сравнению с оригиналом ТТХ, одинаково хорошо гуглятся по ключевым словам 3g/4g router как на ebay, так и на aliexpress). Сегодня я хотел расказать, как я довел до ума роутер за 6,5$ и потратил на это 20$. Зачем? А примерно за тем же, зачем из бисера плетут браслетики — чтобы микромоторику потренировать и зрение подсадить (корпуса компонентов в основном 0402 🙂 ). Большей частью ради спортивного интереса, хотя немаловажно и то, что роутерчик этот, на сегодняшний день — моя любимая база для различных проектов (даже несмотря на сложность доступа к GPIO), вместо «народных» NEXX WT3020F, TP-Link TL-MR3020, TP-Link TL-WR703N и иже с ними. Причиной тому в первую очередь является его размер и форма печатной платы, удобная для встраивания куда угодно (на фото можно оценить размеры). В дальнейшем, чтобы избежать путаницы терминологии, буду называть показанное ниже устройство так, как его называет wiki openwrtA5-V11.


Сердцем этого устройства является SoC Ralink RT5350F с частотой 360 МГц, на борту имеется полноценный USB-host, 4 Мб флеш-памяти и 32 Мб оперативной памяти SDRAM с частотой 166 MHz (это кстати странно, потому что даже древний D-link DIR-320 на борту имел более шуструю DDR). Некоторые клоны клона имеют 16 Мб памяти и не пригодны без наращивания RAM для описанных в статье манипуляций.
Как водится, на картинке ниже показан оригинальный роутер Hame A15.

Оригинал Hame A15

image

А вот так выглядит его улучшенная китайская копия (в конце статьи можно увидеть еще одну копию копии, но в 10 раз дороже 🙂 ).

Китайский клон Hame A15

image

Внутренности прошивки уже на хабре освещались, поэтому останавливаться на особенностях разметки не буду. Подробно проблемы и решения описываются и на 4pda. Информации много, но она разбросана невероятно. Надеюсь в этой статье собрать ключевые моменты, опираясь на опыт ребят-«гигантов роутероводства» :).
Ответ на вопрос «а зачем это (роутер) нужно неискушенному пользователю» процитирую пользователя ded1971:

Общепотребительские свойства — универсальный компактный 3G-WiFi-роутер. Удобно брать в поездки, подключать 3G/4G- модем, организовывать свою WiFi-сеть, либо использовать в качестве репитера для усиления WiFi, подключать интернет в смарт-телевизоре, если нет встроенного WiFi. В общем, куча полезных функций за смешные деньги.

Ну и сравнение с вездесущей и всенародно любимой ES8266

Часть первая, подготовительная

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

Вот так выглядят россыпи радиаторов…

Для крепления радиатора я решил использовать термоклей. Из доступных в наших краях — был только «одноразовый» Алсил-5, который несмотря на достаточно неплохую теплопроводность совершенно не подразумевает длительного хранения. Засыхает за пару дней (а если повезет, так можно купить уже засохший). Поэтому на том же самом aliexpress были заказаны две разновидности термоклея для радиаторов мощных светодиодов — Halnziye HY910 и Hcbonyx HC-910.

Китайские ядреные термоклея

Отзывы о них на специализированныз форумах неплохие, хотя специалисты все-таки для серьезных задач рекомендуют использовать двухкомпонентные эпоксидные термоклея, вроде Thermopox 85CT. Но его еще пойди найди…
На всякий случай ниже приведу сравнительные характеристики теплопроводностей используемых мной термоклеев, найденные в Интернете.

Термоклей Halnziye HY910 — теплопроводность 0.975W/m-K
Термоклей Алсил-5 — теплопроводность 1,4-1,6W/m-K
Термоклей hcbonyx HC-910 — теплопроводность 1.7W/m-K

И общее фото 🙂

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

Разметка и обрезка пластикового корпуса под радиатор 12х12




Проблему с питанием народ решает установкой вместо штатной RC-цепочки роутера специализированных микросхем мониторов питания, например КР1171СП28, DS1816 или MAX809TTR. Мне сразу понравился вариант с использованием супервизоров питания фирмы Maxim. Две основные задачи, которые позволяют решать супервизоры — это удержание контроллера в состоянии сброса до тех пор, пока напряжение питания не достигнет заданного значения и не стабилизируется, либо сброс контроллера при снижении напряжения питания ниже критического уровня или при внезапном провале напряжения. Также из плюсов данного типа супервизоров, можно отметить то, что для установки микросхемы не требуются дополнительные компоненты, и по размерам этот полупроводниковый приборчик ложится на места снятых элементов (да-да, предварительно нужно удалить ненужные уже резисторы и конденсаторы, а потом еще и пройтись оплеткой по контактным площадкам).

Удаляемые комоненты — китайская RC-цепь

Поиск на aliexpress ничего не дал, зато дал поиск по каталогу ЧИПиДИП, где обнаружились формирователи импульса (сброса) MAX809TEUR+T (с напряжением 3.08 вольт), один из которых и был успешно припаян в роутер.

Установка формирователя импульса (сброса) MAX809TEUR+T




Кстати, не могу не упомянуть китайский USB-паяльник, без которого я бы эту процедуру никогда не провернул (даже с хваленой микроволной…).
Чтобы не гонять впустую паяльник, одновременно я решил припаять к контактным площадкам консоли UART (пятачки) контактную колодку для упрощения подключения dupont-овскими проводками от Arduino-конструкторов. Идеально совпали пины колодки с шагом контактов 2 мм (стандартные широкораспространенные 2,54 мм — уже широковаты). Вот такие, например. Чтобы было удобнее припаивать, я с помощью пинцета загнул кончики контактов буквой Г и залудил пятачки на плате роутера.

Подготовка UART-консоли

Потом достаточно было установить колодку на пятачки и слегка прогреть место стыка до расплавления припоя. Важно не переборщить (что легко сделать с мощным паяльником), иначе пятачки могут просто поотваливаться от перегрева. На этот случай на картинке показано резервные test-point, куда можно подпаяться.

Резервные точки UART-консоли

Кстати, можно контакты и другим методом припаять, но корпус уже просто так не закрыть

Альтернативный вариант припаивания контактов UART

image

На этом подготовительная часть закончилась и началась основная.

Часть вторая, «программаторная»

Как я упоминал в начале статьи, в роутере мало памяти, всего 4 Мб. Мне конечно может кто-то вспомнить и про то, что «640 КБ должно хватить всем». Но провозившись неделю за попытками впихнуть в самостоятельно собираемую openwrt для D-Link DIR-320 все необходимые пакеты (а там ведь 8 Мб (!) ), я теперь точно знаю, что на памяти экономить не стоит. Поэтому было решено менять flash на более емкую и при этом обладающую разумной стоимостью (чтоб не дороже самого роутера). Для замены необходимы чипы с эффективной частотой ( SPI Clock rate) не ниже 100 МГц и напряжением питания 3.3 В. Из производителей — Winbond или любой другой (En=Eon, Mx=Macronix, Pm=PMC, At=Atmel). Из того, что советует openwrt.org — это W25Q128BV, MX25L128(35F), S25FL128P, S25FL129P, GD25Q128C, GD25Q128CSIG. Цифра 128 в маркировке говорит нам о том, что объем флешки 16 Мб (128Mbit=16Mб). Выбор именного такого размера обусловлен тем, что с таким объемом может без танцев с бубном работать дешевый китайский программатор 24/25-х микросхем памяти CH341A (о нем ниже). Для любителей скажу, что можно впаять и 32 Мб (например, W25Q256FV, MX25L25635F, N25Q256A, MX25L25645GM2I-10G и даже 64 Мб (MX25L51245G, MT25QL512AB). Правда хотелось бы отметить, что для 64 Мб модулей ценники на память уже далеко не гуманные. Отмечу, что разговор о микросхемах памяти ТОЛЬКО в корпусе SOP8, как самом удобном для пайки. Можно конечно брать и микросхемы в корпусах WSON8 с контактными площадками вместо ног, но пайка слишком трудоемка, а результат может и не оправдать ожидания.
Прошить микросхему памяти можно несколькими различными способами, в зависимости от того, что имеется в наличии:
а)Имеется диод/конденсатор и параллельный порт (самый простой программатор) — смотрим инструкцию по прошивке на чешском сайте или по-русски.

Так этот простейший программатор из LPT-разъема и пары деталей выглядит в жизни

image

б)Имеется любое Arduino (Pro, Micro, Nano и т. п.) — готовим из них программатор для наших SPI-флешей по следующему рецепту.
в) Если имеется Raspberry Pi — смотрим здесь
г)имеется в наличии «народный» программатор USBASP (о котором когда-тописали на Хабре — готовим его по рецепту №2.
д)имеется только желание и немного денег — покупаем на aliexpress китайский программатор CH341A для 24/25 SPI флеш и спокойно начинаем работать.
Сама флешка подсоединяется к программатору (-ам) следующим образом:

Цоколевка микросхемы памяти

image

Флешку можно выпаивать и подключать к колодкам/переходникам программатора, а можно к ней подпаять уже упомянутые dupont-провода от Arduino, а уже провода подключать к программатору. «Рабочими» у флеш являются контакты MISO, MOSI, CLK, CS, VCC и GND. Если программирование ведется без снятия микросхемы с платы — лучше всего на время работы отпаять и немного приподнять над плоскостью платы контакт VCC. После окончания работы — прижать ножку к плате и припаять.
Так как желания искать порт LPT у меня не было, я решил действовать по последнему варианту д) и приобрести дополнительную железку в свой парк. Называется эта штука CH341A и представляет собой, как ни странно, программатор на основе чипа CH341, за авторской разработкой «широко известной» китайской мегакорпорации Jiangsu QinHeng Ltd. 🙂

Принципиальная схема программатора

image

При заказе только программатора, настоятельно рекомендую дополнительно прикупить переходник с корпусов SOP8/SOIC8 на корпуса DIP и клипсу-прищепку для работы с микросхемами.

Переходник и клипса


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

Так выглядит готовый набор

По собственному опыту скажу, что клипса-прищепка работает через раз и скорее всего микросхему придется отпаять и посадить в адаптер SOP8-DIP или даже припаять к контактным площадкам на самом программаторе. Я снимал родную микросхему памяти с помощью термовоздушной паяльной станции, а тем у кого такого оборудования нет — рекомендую настоятельно пользоваться методом со сплавом Розе. После работы обязательно остатки сплава Розе оплёткой уберите с платы и микросхем, от греха подальше.
Снятую микросхему подсоединяем к программатору (у кого как и чем получается). Мне припаивать было лень, честно пытался зажать клипсой, в итоге ножки сильно приплюснулись пружиной клипсы. Но благо припаивать назад я 4 Мб флеш не собирался, а хороший контакт должен был обеспечить беспроблемное считывание. Так оно и произошло. Nota bene для тех кто припаивает микросхему — внимательно смотрите на циферки на платах-переходниках и в соответствии с ними припаивайте. В случае клипсы — красный провод на шлейфе ключевой и приходится на ножку с номером 1.

Схвачено и успешно от…





Разобравшись с железом, приходится разбираться с софтом. Как я уже писал, на хабре этот программатор периодически всплывает в статьях. Откуда ясно, что работает он без проблем со всеми современными операционными системами (например, в Windows 10 драйверы подтягиваются из сети автоматически, а в Linux они имеются в ядре уже несколько лет). Но, но мало ли что случится, вот здесь можно найти драйвера (для режима Serial/Parallel) отдельными файлами на всякий случай. Для программирования в Windows чаще всего используется китайский софт авторства некоего SkyGz. Для Linux имеются открытые проекты ch341prog (SPI) и ch341eepromtool (I2C), можно использовать и «народный» flashrom. Так как я использую Windows, а отзывы о «китай-проге» у большинства пользователей не особо позитивные (особенно о новых 1.28…1.30 версиях), то чтобы судьбу не искушать, считывание микросхемы проводилось с помощь простого, надежного, русского программатора AsProgrammer версии 1.4.0. Кстати, если что-то вдруг чего-то не понравилось/не понятно, или например, нужно добавить новую микросхему — можно запросто написать в теме «техподдержки» и получить быстрый feedback.

Почему пока НЕ стоит покупать флеш-память больше 16 Мб

Позволю себе небольшое лирическое отступление и объясню свое нежелание устанавливать флешку размером 32 Мб (разница в цене с 16 Мб некритичная). Дело в том, что ни в китайских программах (версии 1.29 или 1.30), ни в упомянутом AsProgrammer не заявлена поддержка микросхем аля W25Q256FV, MX25L25635F, N25Q256A, MX25L25645GM2I-10G. Возможно, причина этого в том, что для SPI-флеш максимальный объем равен 128 Мбит (максимум, который можно адресовать 3-байтовым режимом адресации, принятым JEDEC). И здесь вариантов не так уж и много. Можно к примеру записать с помощью модифицированной flashrom первую половину чипа w25q256 (размером в 16 Мб), как знакомый этой программе 16 Мб чип w25q128 и засунуть в этот объем правильный bootloader (Breed, например) и factory-разделы со «служебной» информацией (mac-адрес роутера, калибровка wi-fi и т. п.). А уже загрузившись в режиме бутлоадера (в случае успешного распознавания им всего объема микросхемы) — из него можно было бы прошить скомпилированную заранее прошивку. Можно вообще закупить на будущее флешек и ждать, пока поддержка таких объемов появится в ломанных версиях китайского софта к ch341a или когда допилится flashrom. Подытоживая, лучше взять 16 Мб 🙂 ибо «устав тянуться к звездам, подними то, что валяется под ногами».

Работа с AsProgrammer предельно проста, сначала выбираем в меню «Микросхема» свою микросхему, а затем жмем на кнопочку «Читать». Пару-тройку минут и дамп у нас в кармане 🙂

Считывание дампа с заводской микросхемы 4 Мб

Заводской фулфлеш состоит из загрузчика, пары разделов для хранения разных настроек и самой прошивки. Сохраненный файл дампа нужно открыть в любом HEX-редакторе (я вот привык пользоваться WinHex) и по адресу 105720 (адрес в десятичной системе) меняем 40 00 на 00 01 для 16 Мб флеш (ну или на 80 00, если вдруг вы решили съэкономит десяток центов и купить микросхему объемом 8 Мб). Сохраняем модифицированную прошивку для дальнейшего использования. Кстати, адрес 105720 не является точкой какой-то привязки и там вполне могут находится другие цифры, особенно в том случае, если вы уже пытались разблокировать загрузчик для прошивки openwrt и т. п. Поэтому, если вдруг ничего не получается, и заветных «40 00» по адресу 105720 не видно, ищем в меню шестнадцатиричного редактора поиск по HEX-значениями и вбиваем туда ключевую комбинацию байтов «00 00 16 40». Она и выведет к нужным цифрам/адресу.

В поисках заветной комбинации 00 00 16 40

Зачем это было нужно? А затем, чтобы модифицированный загрузчик позволил перепрошивать роутер через UART по TFTP, используя прошивки большего размера.
Далее сохраненную прошивку заливаем в заранее купленную флешку бОльшого размера начиная с адреса 0 (с начала). В случае успешного программирования впаиваем микросхему памяти на место и переходим к следующему этапу. Кстати, программируя купленную микросхему, мне стало жалко ее зажимать клипсой и пришлось припаять на колодку 🙂

Заливка прошивки

Часть третья, openwrt-шная

Следующий этап — сборка собственной версии openwrt. Если бы мне не были нужны некие специфичные пакеты, которые невозможно найти в существующих официальных (и «от энтузиастов») sysupgrade-прошивках, то можно было просто скачать первый попавшийся вариант прошивки для своей модели роутера и загрузить ее через УЖЕ понимающий большие микросхемы памяти модифицированный бутлоадер или же по-старинке использовать выше упомянутый программатор. Но мне нужно встроить в прошивку своего роутера несколько нехарактерных пакетов, поэтому пришлось идти более сложным путем и собирать openwrt под себя. Процедура эта довольно хорошо описана для множества моделей роутеров (например, для Tp-Link TL-WR741ND на Хабре, Tp-Link TL-MR3020, D-Link DIR320A1, тысячи их…), поэтому ограничусь кратким перечислением основных шагов (подробнее можно глянуть тут и тут).
Если дома машины с *nix не имеется, то первым делом устанавливаем на VirtualBox Ubuntu x64 (ну или кому-что).
Обновляем информацию об установленных пакетах
sudo apt-get update
Доустанавливаем необходимое. Для Ubuntu 16.04 LTS это выглядит так:
sudo apt-get install build-essential subversion mercurial libncurses5-dev zlib1g-dev gawk gcc-multilib flex git-core gettext libssl-dev unzip
Копируем исходники выбранной прошивки к себе на компьютер командой git clone. Под спойлером — ссылки на исходники разных версий openwrt.

Git-репозитории проектов OpenWrt и LEDE

18.06.1
git clone git://github.com/openwrt/openwrt.git -b v18.06.1
18.06.0
git clone git://github.com/openwrt/openwrt.git -b v18.06.0
17.01.5
git clone git://github.com/openwrt/openwrt.git -b v17.01.5
17.01.4
git clone git://github.com/openwrt/openwrt.git -b v17.01.4
17.01.3
git clone git://github.com/openwrt/openwrt.git -b v17.01.3
17.01.2
git clone git://github.com/openwrt/openwrt.git -b v17.01.2
17.01.1
git clone git://github.com/openwrt/openwrt.git -b v17.01.1
17.01
git clone git://github.com/openwrt/openwrt.git -b v17.01.0
15.05.1
git clone git://github.com/openwrt/archive.git -b v15.05.1
15.05
git clone git://github.com/openwrt/archive.git -b v15.05
14.07
git clone git://github.com/openwrt/archive.git -b v14.07
12.09
git clone git://github.com/openwrt/archive.git -b v12.09

Скачанное добро у нас развернется в директории openwrt. Переходим в нее командой cd, подкачиваем и обновляем дополнительные компоненты командой
./scripts/feeds update -a && ./scripts/feeds install –a
Теперь самое время поправить «важные файлы» для того, чтобы компилятор правильно смог собрать прошивку для нашей увеличенной флешки, а не застопорился, если вдруг размер превысит положенные прописанные 4 Мб. Для этого делаем следующее. В скачанной директории с исходниками openwrt в файле target/linux/ramips/dts/A5-V11.dts заменяем на свои значения обозначенные на картинке ниже (я использовал редактор nano, можно и через F4 в mc).

Правка файла *.dts под память увеличенного размера (для 15.05.1)

Сохраняем файл, в консоли запускаем
make defconfig
и применяем стандартные параметры для профиля, затем make menuconfig. Здесь основной ключевой момент — выбор правильного профиля:

Target System: Ralink RT288x/RT3xxx
Subtarget: RT3x5x/RT5350 based boards
Target Profile: A5-V11

а дальше — по аналогии со сборкой openwrt для других роутеров (ссылки я приводил выше), модифицируем набор пакетов нужным нам образом и сохраняем конфигурацию (в файл .config). Кстати, если планируется часто изменять конфигурации, то можно каждый раз делать резервную копию командой scripts/diffconfig.sh >mydiffconfig, так мы каждый раз сохраняем свои изменения конфигурации в файл mydiffconfig. После составления нужной конфигурации (и выбора всех необходимых пакетов) запускаем сборку командой make.
Из-за самостоятельной компиляции ядра можно столкнуться с тем, что многие пакеты из официальных репозиториев для установки не подойдут. Это следует учитывать и чаще устанавливать флаг “M” напротив нужного пакета. Потом эти пакеты можно найти в папке openwrt/bin/ramips/packages и закинуть на какой-нибудь HTTP сервер для создания своего, личного репозитория с блекджеком и… для OpenWRT :).
Вот кстати вспомнил, что очень удобно команду make запускать в следующем виде:
make V=99 2>&1 | tee build.log | grep -i error
В таком случае компилятор сохраняет полную подробную копию отладочной информации о сборке в файл build.log в каталоге openwrt и показывает на экране только ошибки. Потом этот лог удобно прикреплять на форумах, чтобы найти причину ошибки или задать вопрос. Кроме того, она замечательно позволяет идентифицировать пакеты, на которых компилятор прекращает работу. Найти текст, упоминающийся в логе можно поиском по директории с помощью команды
grep –Hr «ТЕКСТ» <путь к директории поиска>  | cut –d: -f1  |  sort  -u
Но искренне надеюсь, что компиляция пройдет успешно и выискивать ошибки не придется. После завершения процесса в папке bin каталога openwrt можно будет найти файлы прошивки, что-то вроде openwrt-ramips-rt305x-a5-v11-squashfs-sysupgrade.bin. Забираем этот файл и переходим к завершающей стадии.

Часть четвертая, консольная

После того, как прошивка собрана в недрах виртуальной машины, все что остается — закинуть ее в роутер. Мне нравится вариант с UART (не даром же я к контактным пятакам припаивал колодку). Для подключения нужен либо USB to TTL UART адаптер, либо хоть дата-кабель от старого сименса. У меня, к счастью, был такой переходник на микросхеме FT232 (подходят также микросхемы CP2102 и вездесущая CH340). Цоколевка контактных площадок показана на фото.

Основная и резервная распиновка UART-консоли


Подключаем контакты к адаптеру по следующей схеме:

GND роутера → GND адаптера
RX роутера → резистор 470 Ом-1 кОм → TX адаптера
TX роутера → RX адаптера

Объясню, зачем нужен резистор. У меня при включении роутера подключенного к адаптеру, он зависал и начинал греться.

Так все это подключалось…

При этом в терминал успевало вывалиться что-то вроде

ϟr▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒1▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒A▒▒(0▒▒▒▒▒
?▒▒▒{▒F▒▒▒▒▒^▒▒B▒▒▒KӉSP@▒(Y <0▒0▒$▒@▒▒▒ ▒▒▒怘▒▒ff▒▒▒▒▒`▒▒▒▒▒▒▒▒▒▒▒x▒▒x▒▒▒▒▒x~`▒▒怘▒▒▒▒▒x▒▒f▒▒▒▒▒f▒▒`ff▒▒▒▒▒▒▒▒▒fx▒▒▒f~f▒f▒▒`fxf▒▒▒▒`fx▒▒~▒x▒f▒▒f~f▒`▒f▒fx▒~▒f▒xf▒~▒▒fx▒▒x▒▒▒xx▒▒▒▒xx▒▒▒▒x▒▒▒xf▒▒`x▒▒▒▒▒▒fxf▒x▒▒`▒▒▒f▒▒f▒▒f▒fxx▒x▒枘▒x▒f▒▒

Резистор (можно подключать любой, в диапазоне от 470 Ом до 1 кОм) все исправил. А вот здесь, например, автор вообще советует использовать для подключения к UART для развязки использовать микросхему-изолятор цифровых сигналов AduM1201. Но мне хватило и резистора, чего и вам желаю.

UART и AduM1201

image

Принимаем сигнал от роутера с помощью программы-терминала на ПК. Как-то с народным любимцем Putty у меня не сложилось, и я использую отличную программу Terminal. Выбираем нужный виртуальный COM-порт (на котором сидит наш USB-UART), выставляем параметры:

Speed: 57600
Data Bits: 8
Parity: None
Stop Bits: 1

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

Логи загрузчика A5-V11 as is

U-Boot 1.1.3 (Apr 11 2013 — 00:10:51)
Board: Ralink APSoC DRAM: 32 MB
relocate_code Pointer at: 81fb4000
spi_wait_nsec: 42
spi device id: ef 40 18 0 0 (40180000)
Warning: un-recognized chip ID, please update bootloader!
raspi_read: from:30000 len:1000
.*** Warning — bad CRC, using default environment
============================================
Ralink UBoot Version: 3.6.0.0

ASIC 5350_MP (Port5<->None)
DRAM_CONF_FROM: Boot-Strapping
DRAM_TYPE: SDRAM
DRAM_SIZE: 256 Mbits
DRAM_WIDTH: 16 bits
DRAM_TOTAL_WIDTH: 16 bits
TOTAL_MEMORY_SIZE: 32 MBytes
Flash component: SPI Flash
Date:Apr 11 2013 Time:00:10:51
============================================
icache: sets:256, ways:4, linesz:32 ,total:32768
dcache: sets:128, ways:4, linesz:32 ,total:16384
##### The CPU freq = 360 MHZ ####
estimate memory size =32 Mbytes
Please choose the operation:
1: Load system code to SDRAM via TFTP.
2: Load system code then write to Flash via TFTP.
3: Boot system code via Flash (default).
4: Entr boot command line interface.
7: Load Boot Loader code then write to Flash via Serial.
9: Load Boot Loader code then write to Flash via TFTP.
4 3 2 1
You choosed 3
0
3: System Boot system code via Flash.
## Booting image at bc050000…
raspi_read: from:50000 len:40
. Image Name: MIPS OpenWrt Linux-3.7.5
Created: 2013-04-11 14:41:22 UTC
Image Type: MIPS Linux Kernel Image (lzma compressed)
Data Size: 915849 Bytes = 894.4 kB
Load Address: 80000000
Entry Point: 80000000
raspi_read: from:50040 len:df989
… Verifying Checksum… OK
Uncompressing Kernel Image… OK
No initrd
## Transferring control to Linux (at address 80000000)…
## Giving linux memsize in MB, 32
Starting kernel…
[ 0.000000] Linux version 3.7.5 (lich@lich-pc) (gcc version 4.6.4 20121210 (prerelease) (Linaro GCC 4.6-2012.12) ) #2 Thu Apr 11 22:41:02 CST 2013
[ 0.000000] bootconsole [early0] enabled
[ 0.000000] CPU revision is: 0001964c (MIPS 24KEc)
[ 0.000000] Ralink RT5350 id:1 rev:3 running at 360.00 MHz
[ 0.000000] Determined physical RAM map:
[ 0.000000] memory: 02000000 @ 00000000 (usable)
[ 0.000000] User-defined physical RAM map:
[ 0.000000] memory: 02000000 @ 00000000 (usable)
[ 0.000000] Initrd not found or empty — disabling initrd
[ 0.000000] Zone ranges:
[ 0.000000] Normal [mem 0x00000000-0x01ffffff]
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x00000000-0x01ffffff]
[ 0.000000] Primary instruction cache 32kB, VIPT, 4-way, linesize 32 bytes.
[ 0.000000] Primary data cache 16kB, 4-way, VIPT, no aliases, linesize 32 bytes
[ 0.000000] Built 1 zonelists in Zone order, mobility grouping on. Total pages: 8128
[ 0.000000] Kernel command line: board=MPR-A1 console=ttyS1,57600 mtdparts=spi0.0:192k(u-boot)ro,64k(u-boot-env)ro,64k(factory)ro,896k(kernel),2880k(rootfs),3776k@0x50000(firmware) rootfstype=squashfs,jffs2 mem=32M
[ 0.000000] PID hash table entries: 128 (order: -3, 512 bytes)
[ 0.000000] Dentry cache hash table entries: 4096 (order: 2, 16384 bytes)
[ 0.000000] Inode-cache hash table entries: 2048 (order: 1, 8192 bytes)
[ 0.000000] __ex_table already sorted, skipping sort
[ 0.000000] Writing ErrCtl register=00023ff6
[ 0.000000] Readback ErrCtl register=00023ff6
[ 0.000000] Memory: 29632k/32768k available (1969k kernel code, 3136k reserved, 473k data, 180k init, 0k highmem)
[ 0.000000] SLUB: Genslabs=9, HWalign=32, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
[ 0.000000] NR_IRQS:48
[ 0.000000] console [ttyS1] enabled, bootconsole disabled
[ 0.000000] console [ttyS1] enabled, bootconsole disabled
[ 0.010000] Calibrating delay loop… 239.61 BogoMIPS (lpj=1198080)
[ 0.080000] pid_max: default: 32768 minimum: 301
[ 0.080000] Mount-cache hash table entries: 512
[ 0.090000] NET: Registered protocol family 16
[ 0.100000] MIPS: machine is HAME MPR-A1
[ 0.130000] bio: create slab <bio-0> at 0
[ 0.140000] Switching to clocksource MIPS
[ 0.150000] NET: Registered protocol family 2
[ 0.160000] TCP established hash table entries: 1024 (order: 1, 8192 bytes)
[ 0.180000] TCP bind hash table entries: 1024 (order: 0, 4096 bytes)
[ 0.190000] TCP: Hash tables configured (established 1024 bind 1024)
[ 0.200000] TCP: reno registered
[ 0.210000] UDP hash table entries: 256 (order: 0, 4096 bytes)
[ 0.220000] UDP-Lite hash table entries: 256 (order: 0, 4096 bytes)
[ 0.230000] NET: Registered protocol family 1
[ 0.280000] squashfs: version 4.0 (2009/01/31) Phillip Lougher
[ 0.290000] jffs2: version 2.2 (NAND) (SUMMARY) (LZMA) (RTIME) (CMODE_PRIORITY) © 2001-2006 Red Hat, Inc.
[ 0.320000] msgmni has been set to 57
[ 0.320000] io scheduler noop registered
[ 0.330000] io scheduler deadline registered (default)
[ 0.340000] Serial: 8250/16550 driver, 2 ports, IRQ sharing disabled
[ 0.360000] serial8250: ttyS0 at MMIO 0x10000500 (irq = 13) is a 16550A
[ 0.370000] serial8250: ttyS1 at MMIO 0x10000c00 (irq = 20) is a 16550A
[ 0.390000] ramips-spi ramips-spi.0: master is unqueued, this is deprecated
[ 0.410000] m25p80 spi0.0: found w25q128, expected pm25lq032
[ 0.420000] m25p80 spi0.0: w25q128 (16384 Kbytes)
[ 0.430000] 6 cmdlinepart partitions found on MTD device spi0.0
[ 0.440000] Creating 6 MTD partitions on «spi0.0»:
[ 0.450000] 0x000000000000-0x000000030000: «u-boot»
[ 0.460000] 0x000000030000-0x000000040000: «u-boot-env»
[ 0.480000] 0x000000040000-0x000000050000: «factory»
[ 0.490000] 0x000000050000-0x000000130000: «kernel»
[ 0.500000] 0x000000130000-0x000000400000: «rootfs»
[ 0.520000] mtd: partition «rootfs» set to be root filesystem
[ 0.530000] mtd: partition «rootfs_data» created automatically, ofs=380000, len=80000
[ 0.550000] 0x000000380000-0x000000400000: «rootfs_data»
[ 0.560000] 0x000000050000-0x000000400000: «firmware»
[ 0.580000] ramips-wdt ramips-wdt: timeout value must be 0 < timeout <= 35, using 35
[ 0.600000] TCP: cubic registered
[ 0.610000] NET: Registered protocol family 17
[ 0.620000] 8021q: 802.1Q VLAN Support v1.8
[ 0.650000] VFS: Mounted root (squashfs filesystem) readonly on device 31:4.
[ 0.660000] Freeing unused kernel memory: 180k freed
[ 5.100000] input: gpio-keys-polled as /devices/platform/gpio-keys-polled/input/input0
[ 5.210000] Button Hotplug driver version 0.4.1
— preinit — Press the [f] key and hit [enter] to enter failsafe mode

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

Please choose the operation:
1: Load system code to SDRAM via TFTP.
2: Load system code then write to Flash via TFTP.
3: Boot system code via Flash (default).
4: Entr boot command line interface.
7: Load Boot Loader code then write to Flash via Serial.
9: Load Boot Loader code then write to Flash via TFTP.

Нужно быстренько успеть нажать цифру «2». Тогда увидим следующее:

You choosed 2
2: System Load Linux Kernel then write to Flash via TFTP.
Warning!!! Erase Linux in Flash then burn new one. Are you sure?(Y/N)
Please Input new ones /or Ctrl-C to discard
Input device IP (10.10.10.123) ==:10.10.10.123
Input server IP (10.10.10.3) ==:10.10.10.3
Input Linux Kernel filename () ==: openwrt-ramips-rt305x-a5-v11-squashfs-sysupgrade.bin

Нажимаем последовательно «Y», ввод, ввод, «openwrt-ramips-rt305x-a5-v11-squashfs-sysupgrade.bin». В последнем случае нужно будет вручную прописать название нашей прошивки, которую нужно будет закинуть на предварительно запущенный tftp сервер. Я для этой цели использовал программу TFTPD64. После активации консольной загрузки прошивки нужно соединить наш A5-V11 c компьютером, у которого IP адрес совпадает с указанным в консоли. Кстати для подключения напрямую (аля «usb-флешка») я наделал себе переходничков RJ45-RJ45. Их очень удобно носить вместе с роутером.

Переходники RJ45-RJ45 и их схема


Роутер прошьется и после перезагрузки будет готов к выполнению новых боевых задач. Кстати, если с консолью прошить не удалось — возвращаемся к «программатороной» части статьи и зашиваем прошивку openwrt-ramips-rt305x-a5-v11-squashfs-sysupgrade.bin через старый добрый AsProgrammer, но уже не с адреса 0, а с 327680 (!).

Часть пятая, информационная («на заметку»)

В конце повествования хотелось бы сказать пару слов о приспособлениях которые использование A5-V11 нашего делают более комфортным.
В первую очередь это U-образный переходник на USB порт. Незаменимое устройство, при совместном использовании роутера с USB-флешкой/карт-ридером/3g-4g модемом/SDR тюнером наконец 🙂

Так переходнички выглядят вживую




Второй важной «вехой» при работе с описываемы мини-роутером является USB-хаб. Да с очень непростой компоновкой USB-портов. Вертикальная компоновка этого пассивного хаба так и подталкивает к покупке Bluetooth «грибка» на каком-нибудь CSR8510. Мне еще с детства нравится концепция промышленных ПК от Octagon — MicroPC — и их вертикальные PC/104 «бутерброды». С упомянутым хабом есть возможность сделать что-то подобное (кстати, если кто-то из читателей нашел на просторах интернет-аукционов что-то подобное интересное — прошу поделиться в коммантариях). Ну а пока заказ еще в пути, без живых фото, ограничусь картинкой из Интернет-ов

Удобный для монтажа USB-хаб

<img src=»» alt=«image»/>

Ну и напоследок самое вкусное. Есть в мирУ такая новозеландская контора CloudStore, известная своей продукцией под брендом Airconsole. Хороши эти устройства, помимо всего прочего и тем, что единственные абсолютно легально и за разумные деньги подключить к iOS всевозможные RS232 логгеры. Очень крутая полезная штука, в том числе для автоматизации старых приборов и оборудования, управляемых посредством RS232. Упоминал об этих крутых девайсах и Хабр.
А вот так пишут о себе они сами 🙂

Если вам требуется управление удаленным оборудованием через COM/USB порты, то зачем тратить сотни или тысячи долларов на терминальные серверы Moxa*, Perle* или Opengear*? AirConsole имеет все возможности удаленного доступа и управления для серверов или сетевого обоудования, но гораздо портативнее и потребляет минимум электроэнергии.

Так вот, теперь внимание. Ниже приведены интересные картинки. Как говорится «найдите 10 отличий» 🙂

Сравнить Airconsole Mini (69$) и Hame A15 (6.9$)


image

Сравнить Airconsole Standart (79$) и Hame MPR-A1 (7.9$)


image

Сравнить Airconsole XL (139$) и Hame MPR-A3 (13.9$)


image

Меня это заинтриговало, и конечно же я честно попросил у производителя их устройство на тест, но ответа не дождался. Пришлось работать методом мысленного эксперимента 🙂 Ну и читать немного. Как говорил Генри Форд «Воздух полон идей. Они постоянно стучатся к вам в голову. Вы просто должны знать что вы хотите, затем забыть это и заниматься своим делом. Идея придёт внезапно. Так было всегда«.
Поэтому в чистом итоге ответ следующий «Китайские роутеры A5-V11 можно превратить в Airconsole». Вот здесь мельком этот вопрос обсуждался и с помощью заливки дампа ребята превращают свои Hamе за 6$ в Airconsole за 69$. И правильно, не Zyxel Keenetic 4G II единым жив энтузиаст.
Кстати, если вдруг среди читателей есть владельцы оригинальных AirConsol-ей, которые их уже вскрывали — с удовольствием приму в дар фото плат и дополню статью 🙂
Ну вот, в принципе и все, что хотелось сказать. Многое из написанного здесь применимо для другого (компактного) железа и надеюсь будет полезно «широкому кругу читателей» 🙂

Использованные источники


ссылка на оригинал статьи https://habr.com/post/425281/

Как Lisp стал языком программирования для Бога

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

Однако есть один язык, который странным образом вызывает всеобщее уважение: Lisp. Крестоносцы клавиатур, готовые атаковать любого, кто посмеет заявить, что какой-либо язык лучше других, сходятся в том, что Лисп находится на другом уровне. Он выходит за пределы утилитарных критериев, по которым судят другие языки, поскольку средний программист никогда не использовал Лисп для создания чего-либо практического, и, вероятно, никогда не будет этого делать, однако же, уважение к Лиспу настолько глубокое, что ему часто приписывают мифические свойства. Всеми любимые комиксы xkcd изображали таким образом Лисп как минимум дважды: в одном комиксе персонаж достигает Лисп-просветления, которое помогает ему познать фундаментальную структуру Вселенной. В другом старый программист в халате передаёт стопку круглых скобок своему падавану, объясняя, что это – «элегантное оружие для более цивилизованных времён», намекая на присущие Лиспу оккультные возможности Силы.

Ещё один прекрасный пример – пародия Боба Канефского на песню «Бог живёт на Терре». Его пародия написана в середине 90-х и называется «Вечное пламя». Она описывает, как, по-видимому, Бог создал мир при помощи Лиспа. Далее приводим отрывок, а полную версию можно найти на сайте GNU Humor Collection:

Ведь Бог писал на Лиспе
Когда заполнил листья зелёным.
Фрактальные цветки и рекурсивные корни:
Самый красивый хак из виденных мною.
А когда я изучаю снежинки,
И не нахожу двух одинаковых,
Я знаю, что Бог любит язык
Со своим собственным четырёхбуквенным названием.

Говорю только за себя, но мне кажется, что культурный мем «Лисп – это тайное волшебство», это самое странное и интересное явление. Лисп был задуман в башне из слоновой кости в качестве инструмента для исследований искусственного интеллекта, поэтому он всегда будет немного незнакомым и загадочным для простых программистов. Однако сейчас программисты подначивают друг друга «попробуй Лисп перед тем, как умереть», как будто это какое-то психоделическое средство, расширяющее сознание. Они делают это несмотря на то, что Лисп – второй по возрасту из самых старых языков программирования, которые ещё используют, уступая лишь Фортрану, и то, всего на год. Представьте, что вам поручила бы рекламу какого-то нового языка программирования компания или команда, которая его разработала. Разве не было бы классно суметь убедить всех, что у вашего нового языка есть божественные силы? Но как этого можно было бы достичь? Как язык программирования прославился в роли источника тайного знания?

Как Лисп дошёл до жизни такой?


Обложка журнала Byte, август 1979

Теория А: аксиоматический язык

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

Маккарти начал размышлять о создании языка во время Дартмутского летнего исследовательского проекта по искусственному интеллекту 1956 года. Этот семинар стал непрерывной многонедельной академической конференцией, самой первой в области ИИ. Кстати, именно Маккарти, тогда будучи адъюнкт-профессором по математике в Дартмуре, придумал термин «искусственный интеллект», предлагая провести эту встречу. В конференции участвовало порядка десяти человек. Среди них были Аллен Ньюэл и Герберт Саймон, два исследователя, связанных с RAND Corporation и Университетом Карнеги-Меллона, только что закончившие разработку языка IPL.

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

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

Конечно, сегодня Лисп не напоминает Фортран. За последовавшие несколько лет идеи Маккарти по поводу идеального языка обработки списков развились. Его идеи начали меняться в 1957 году, когда он начал писать процедуры для программы на Фортране, играющей в шахматы. Длительное воздействие Фортрана убедило Маккарти, что в его дизайне было несколько неудачных мест, главное из которых – неуклюжий оператор IF. Маккарти изобрёл альтернативу, условное выражение «true», возвращающее подвыражение A, если заданная проверка прошла успешно, и подвыражение B в другом случае, и выполняющее только то подвыражение, которое было возвращено. Летом 1958 года, когда Маккарти работал над программой, способной на дифференцирование, он понял, что его условное выражение «true» сделало написание рекурсивных функций более простым и естественным. Задача с дифференцированием также подвигла Маккарти на написание функции maplist, принимающей в качестве аргумента другую функцию, и применяющей её ко всем элементам списка. Она была полезной для дифференцирования сумм произвольного количества членов.

Такие вещи на Фортране было не выразить, поэтому осенью 1958 Маккарти задал нескольким студентам задачу по реализации Лиспа. Поскольку теперь Маккарти был адъюнкт-профессором в MIT, все студенты учились в MIT. Переведя идеи в рабочий код, Маккарти со студентами внесли изменения, ещё больше упростившие язык. Самое крупное из них заключалось в синтаксисе Лиспа. Маккарти сначала хотел, чтобы в языке использовались т.н. «М-выражения», слой «синтаксического сахара«, делавшего синтаксис Лиспа похожим на Фортран. Хотя М-выражения можно перевести в S-выражения – простой список, заключённый в скобки, которыми славен Лисп – S-выражения реально были низкоуровневым представлением, предназначавшимся для компьютера. Единственной проблемой было то, что Маккарти обозначал М-выражения при помощи квадратных скобок, а на перфораторе IBM 026, использовавшемся в MIT, квадратных скобок не было. Поэтому команда Лиспа ограничилась S-выражениями, и использовала их для представления не только списков данных, но и применения функций. Маккарти со студентами сделали ещё несколько упрощений, включая переход на префиксную запись и модель памяти, в которой у языка был только один тип real.

В 1960-м Маккарти опубликовал знаменитую работу по Лиспу «Рекурсивные функции символических выражений и их машинное вычисление» [Recursive Functions of Symbolic Expressions and Their Computation by Machine]. К тому времени язык был сокращён до такой степени, что Маккарти понял, что создал «элегантную математическую систему», а не просто ещё один язык программирования. Позже он писал, что многие упрощения в Лиспе превратили его в «способ описания вычисляемых функций, гораздо более точный, нежели машины Тьюринга или общие рекурсивные определения, используемые в теории рекурсивных функций». В своей работе он представил Лисп, как рабочий язык программирования, и как формализм для изучения поведения рекурсивных функций.

Маккарти объяснял Лисп читателям, выстраивая его из небольшого набора правил. Позже Пол Грэм прошёл по следам Маккарти, используя более простой в чтении язык, в своём эссе «Корни Лиспа«. Грэм может объяснить Лисп при помощи лишь семи примитивных операторов, двух различных записей для функций и шести функций высокого уровня, определяемых через примитивные операторы. Способность определить Лисп при помощи такой небольшой последовательности простых правил, безусловно, добавляет ему загадочности. Грэм назвал работу Маккарти попыткой «аксиоматизировать вычисления». Я думаю, что это отличный способ размышлять о привлекательности Лиспа. В других языках явно присутствуют искусственные конструкции, описываемые такими зарезервированными словами, как while, typedef, или public static void, кажется, что описание Лиспа ограничено самой логикой вычислений. Это качество и изначальная связь Лиспа с такой эзотерической областью, как «теория рекурсивных функций», должны объяснять сегодняшний престиж языка.

Теория Б: машина будущего

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

К середине 1970-х исследователям ИИ начало не хватать компьютерной мощности. К примеру, у PDP-10 – всеми любимой машины для работы с ИИ – было 18-битное адресное пространство, которого всё чаще не хватало для ИИ-программ на Лиспе. Многие ИИ-программы к тому же должны были быть интерактивными, а создание крупной интерактивной программы, хорошо работающей на системе с разделением времени, было трудной задачей. Решение, которое первым предложил Питер Дойч из MIT, состояло в разработке специального компьютера для Лиспа. Такие машины должны были дать каждому пользователю выделенный процессор, оптимизированный под Лисп. На них также должна была работать среда разработки, написанная на Лиспе для хардкорных Лисп-программистов. Лисп-машины, придуманные в неудобный момент в конце эры мини-компьютеров, но до начала расцвета микрокомпьютерной революции, были высокопроизводительными персональными компьютерами для элиты программистов.

Некоторое время казалось, что Лисп-машины будут волной будущего. Появилось несколько компаний, начавших состязаться за коммерциализацию этой технологии. Наиболее успешной из них стала Symbolics, созданная ветеранами MIT AI Lab. В 1980-х Symbolics выпустила линейку компьютеров из серии 3600, популярных в области ИИ и в индустриях, где требовались вычисления высокой мощности. В линейке 3600 были компьютеры с большими экранами, растровая графика, интерфейс, использовавший мышь, мощные программы для графики и анимации. Это были впечатляющие машины, позволявшие писать впечатляющие программы. К примеру, Боб Кали, работавший в области исследования робототехники, написал мне через твиттер, что ему удалось реализовать и визуализировать алгоритм поиска пути на Symbolics 3650 в 1985. Он рассказал, что растровая графика и ООП (доступное на Лисп-машинах благодаря расширению Flavors) были новинками в 1980-х. Symbolics находилась на переднем крае.

Но в результате компьютеры Symbolics были безумно дорогими. Symbolics 3600 стоил $110 000 в 1983. Большинство людей могло только дивиться мощности Лисп-машин и магии операторов, писавших на Лисп, издалека. Но они дивились. Журнал Byte несколько раз описывал Лисп и Лисп-машины в период с 1979 по конец 1980-х. В августовском номере от 1979 года, посвящённом Лиспу, главные редактор восторгался новыми машинами, разрабатываемыми в MIT, «с горой памяти» и «передовой операционной системой». Он считал их такими многообещающими, что предыдущие два года, в которые появились Apple II, Commodore PET и TRS-80, казались скучными. Пять лет спустя, в 1985, автор в журнале Byte описывал процесс написания программ на Лиспе для «сложных и чрезвычайно мощных Symbolics 3670», и призывал читателей учить Лисп, заявляя, что он был как «языком, необходимым для большинства исследователей ИИ», так и кандидатом на будущий язык общего назначения.

Я спросил Пола Макджонса, много сделавшего для сохранения Лиспа в Музее компьютерной истории в Маунтин-Вью, о том, когда люди впервые начали говорить о Лиспе, как о даре существ из высшего измерения. Он сказал, что этому, безусловно, способствовали свойства самого языка, но ещё и близкая связь Лиспа и мощных приложений в области ИИ в 1960-х и 1970-х. Когда Лисп-машины стало возможным приобрести в 1980-х, с мощностью Лиспа познакомилось ещё несколько человек, находившихся вне таких мест, как MIT или Стэнфорд, и легенда продолжила расти. Сегодня Лисп-машины и Symbolics мало кто помнит, но они помогали поддерживать ауру загадочности Лиспа вплоть до 1980-х.

Теория В: обучение программированию

В 1985 профессоры из MIT, Гарольд Абельсон и Джеральд Сасман, а также жена Сасмана, Джулии, опубликовали учебник «Структура и интерпретация компьютерных программ» [Structure and Interpretation of Computer Programs]. В учебнике читателей обучали программированию на языке Scheme, диалекте Лиспа. Его использовали в MIT для введения в программирование два десятилетия. Мне кажется, что это учебник, SICP, добавил мистицизма Лиспу. SICP взял Лисп и показал, как его можно использовать для иллюстрации глубоких, почти философских концепций искусства программирования. Эти концепции были достаточно общими для того, чтобы использовать любой ЯП, но авторы SICP выбрали Лисп. В итоге репутация Лиспа была дополнена дурной славой этой странной и гениальной книги, интриговавшей многие поколения программистов (и ставшей очень странным мемом). Лисп всегда был «элегантным формализмом Маккарти»; теперь он стал ещё и языком, «обучающим вас скрытым секретам программирования»).

Стоит немного рассказать о том, насколько странная это книга – поскольку мне кажется, что её странность и странность Лисп сегодня слились в одно. Странность начинается с обложки. На ней изображён волшебник или алхимик, приближающийся к столу, и готовый начать какое-то колдовство. В одной руке у него кронциркуль или компас, в другой – глобус с надписями «eval» и «apply». Женщина напротив него показывает на стол; на фоне висит в воздухе греческая буква лямбда, излучающая свет.

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

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

И не сказать, что книга плохая. Она чудесная. В ней обсуждаются важные концепции программирования на уровне более высоком, чем во всех других прочитанных мною книгах, концепции, о которых я давно задумывался, но не мог описать. Удивительно, что учебник по введению в программирование так быстро может перейти к описанию фундаментальных недостатков ООП и преимуществах функциональных языков, минимизирующих изменяемое состояние. Удивительно, как это превращается в обсуждение того, как потоковая парадигма, наверно, что-то вроде сегодняшней RxJS, может дать вам лучшее обоих подходов. SICP выдяляет самую суть разработки высокоуровневых программ способом, напоминающим исходную работу Маккарти по Лиспу. Первое, что вам хочется сделать, прочтя эту книгу – заставить прочесть её ваших друзей-программистов; если они её найдут, увидят обложку, и не станут читать, то всё, что у них отложится – это то, что некая загадочная штука по имени eval/apply даёт волшебникам особую власть над столами с ногами животных. Меня бы ещё впечатлила их обувь.

Но, возможно, наиболее важный вклад SICP состоял в подъёме Лиспа с уровня забавной диковинки до педагогической необходимости. Задолго до SICP люди советовали друг другу изучить Лисп, чтобы повысить свой уровень как программиста. Номер журнала Byte от 1979 года тому свидетельство. Тот же редактор, восхищавшийся новыми Лисп-машинами в MIT, объяснил, что стоит учить этот язык, поскольку он «представляет другую точку зрения на задачи». Однако в SICP Лисп был представлен не просто как контраст к другим языкам. Его использовали в качестве вводного языка, намекая, что это лучший язык для изучения базовых понятий программирования. Когда сегодняшние программисты советуют друг другу попробовать Лисп до того, как они умрут, они, вероятно, делают это из-за SICP. Ведь Brainfuck тоже, вероятно, предлагает «другую точку зрения на задачи». Но люди вместо этого изучают Лисп, зная, что уже лет двадцать точка зрения Лиспа была настолько полезной, что студентов в MIT учили Лиспу до всех остальных языков.

Возвращение Лиспа

В год выхода SICP Бьёрн Страуструп опубликовал первое издание книги «Язык программирования C++«, принёсшей ООП в массы. Несколько лет спустя рынок Лисп-машин рухнул, и началась зима ИИ. За последовавшие десять с чем-то лет C++ и потом Java стали языками будущего, а Лисп прозябал.

Естественно, невозможно указать, когда точно люди стали снова восхищаться Лиспом. Возможно, это произошло после того, как Пол Грэм, сооснователь Y-Combinator и создатель Hacker News, опубликовал несколько влиятельных эссе, где описывал Лисп, как лучший язык для стартапов. В эссе «Опережая средних» Грэхем утверждал, что макросы Лиспа сделали язык сильнее других языков. Он заявил, что использование Лиспа в своём стартапе Viaweb помогло ему разработать определённые вещи быстрее, чем это смогли сделать конкуренты. Некоторых программистов это убедило. Но большинство не переключилось на Лисп.

Вместо этого всё больше особенностей Лиспа стало попадать во всеми любимые языки. В Python появилась генерация списков. В C# — Linq. В Ruby… ну, Ruby – это и есть Лисп. Как отмечал Грэм ещё в 2001, «язык по умолчанию, встроенный во многие появившиеся позже популярные языки, постепенно развивался в сторону Лиспа». И хотя другие языки постепенно приближаются к Лиспу, сам Лисп как-то поддерживает свою особую репутацию загадочного языка, который мало кто понимает, но все должны изучить. В 1980, в год 20-летия Лиспа, Маккарти писал, что Лисп выжил так долго, поскольку занял «примерный локальный оптимум своего рода в пространстве ЯП». Но это недооценивает реальное влияние Лиспа. Он выживает уже пятьдесят лет потому, что программисты десятилетие за десятилетием нехотя признавали, что это лучший инструмент для своей задачи. Он выжил, даже несмотря на то, что большинство программистов его не используют. Благодаря его происхождению и использованию в исследованиях ИИ, и, возможно, также наследию SICP, Лисп продолжает восхищать людей. И пока мы не сможем представить Бога, сотворившего мир при помощи какого-то более нового языка, Лисп никуда не денется.


ссылка на оригинал статьи https://habr.com/post/428229/