3/03/2018

Implementation of Variational Networks for image reconstruction in tensorflow

Variational Networks for image reconstruction in tensorflow

This is a generic implementation of Variational Network (loop unrolling) for MR image reconstruction, proposed by Hammerick et al.. MRI data used to train the network is introduced and described by Johannes Schmidt and Sebastian Kozerke. The full code is available at github: https://github.com/visva89/VarNetRecon

Running the code

  • Download 2D short axis cardiac MRI dataset https://polybox.ethz.ch/index.php/s/E9FgAzi21iVJiF5
  • Inspect configuration of the network in recon_model_300_066.py. We used:
    • 10 layers
    • 7x7 filters
    • 30 filters per layer
    • 35 knots to parametrize activations
    • cubic interpolation for activations
  • Run example.sh for training, or use saved parameters ckpts/model.ckpt and run recon_model_300_066.py
  • Run matlab script report_imgs.m for illustrations of reconstruction and learned parameters

Demo

For training we used fully sampled 128x128 short axis MR images of the heart with artificially generated smooth phase offset. The k-space was retrospectively undersampled to achieve acceleration factor of ~3. Variational Network reconstruction was compared to total variation (TV) regularized reconstruction (with the optimal regularization weight). recons example
Reconstruction for each layer of the network: recons layers
Learned filters (real part): recons layers
Learned activation functions (integral of activation, i.e. element-wise potentials): recons layers

Important Notes

  • We are reconstructing smaller images compared to original work. Hence different filter sizes are used.
  • Multi coil acquisition as well as coil sensitivity maps are not modeled.
  • Batch size is fixed at configuration time.
  • Cubic interpolation is used by default instead of RBF, since it is more memory efficient. You can choose linear, cubic, or RBF in the network configuration.
  • Maximal estimated filter response is fixed for all layers.

11/22/2013

Constrained linear least squares in Python using scipy and cvxopt. Matlabs lsqlin and lsqnonneg in Python with sparse matrices.

So Matlab has handy functions to solve non-negative constrained linear least squares(lsqnonneg), and optimization toolbox has even more general linear constrained least squares(lsqlin). The great thing about these functions, is that they can efficiently solve problems with large sparse matrices. Well, Python has scipy.optimize.nnls that can handle non-negative least squares as well, but there is no built-in lsqlin alternative, and nnls can't handle sparse matrices. However, you can formulate it as quadratic programming problem, and use scipy.optimize.fmin_slsqp to solve it, but scipy SLSQP implementation can't solve the problem for sparse matrices as well. Luckily there is really great optimization package for Python, called CVXOPT, that can solve quadratic programming problems with sparse matrices. Let's wrap it all up, and even add $\ell_2$ regularization.
The problem of constrained linear least squares is usually stated in following way:

$$\min_x \; \frac12\|Cx-d\|_2^2 + \lambda \|x\|_2^2, $$ $$s.t. \;\; Ax \leq a, \;\;\; Bx = b. $$

using the fact that $\|x\|_2^2 = x^\top x$: $$\frac12\|Cx-d\|_2^2 + \lambda \|x\|_2^2 = \frac12 (Cx - d)^\top(Cx-d) + \lambda x^\top x =$$ $$=\frac12(x^\top C^\top - d^\top)(Cx-d) + \lambda x^\top I x = \frac12 x^\top (C^\top Cx + \lambda I)x - d^\top Cx + \frac12 d^\top d.$$ While minimizing over $x$ we don't care about $d^\top d$, taking $Q = C^\top C + \lambda I$, and $r=d^\top C$ we can rewrite our initial optimization problem as follows:

$$\min_x \; \frac12 x^\top Qx + rx, $$ $$s.t. \;\; Ax \leq a, \;\;\; Bx = b. $$

Now it is obvious, that we have formulated a quadratic program, that can be solved by cvxopt.solvers.qp. Stacking box inequality constraints(Matlab notation lb and ub such that lb<=x<=ub) to matrix $A$ and vector $a$, we can fully emulate lsqlin and lsqnonneg with sparse and dense matrices.

Testing lsqnonneg:
Here is Matlab reference code:

C = [0.0372, 0.2869; 0.6861, 0.7071; ...
     0.6233, 0.6245; 0.6344, 0.6170];
d = [0.8587, 0.1781, 0.0747, 0.8405]';
x = lsqnonneg(C, d)
% output:
% x = [0, 0.6929]
And here is Python code:
import lsqlin
import numpy as np
C = np.array([[0.0372, 0.2869], [0.6861, 0.7071], \
              [0.6233, 0.6245], [0.6344, 0.6170]]);
d = np.array([0.8587, 0.1781, 0.0747, 0.8405]);
ret = lsqlin.lsqnonneg(C, d, {'show_progress': False})
print ret['x'].T
#output:
#[ 2.50e-07  6.93e-01]
So it works perfectly! Disregard tiny value of x[0]=2.5e-07 this can be fixed, by imposing small regularization.

Testing lsqlin:
Matlab reference code:

C = [0.9501    0.7620    0.6153    0.4057
     0.2311    0.4564    0.7919    0.9354
     0.6068    0.0185    0.9218    0.9169
     0.4859    0.8214    0.7382    0.4102
     0.8912    0.4447    0.1762    0.8936];
d = [0.0578, 0.3528, 0.8131, 0.0098, 0.1388]';
A =[0.2027    0.2721    0.7467    0.4659
    0.1987    0.1988    0.4450    0.4186
    0.6037    0.0152    0.9318    0.8462];
b =[0.5251, 0.2026, 0.6721]';
lb = -0.1*ones(4,1);
ub = 2*ones(4,1);
x = lsqlin(C, d, A, b, [], [], lb, ub)
% output:
% x = [-0.1000, -0.1000, 0.2152, 0.3502]
Python code:
import lsqlin
import numpy as np

C = np.array(np.mat('''0.9501,0.7620,0.6153,0.4057;
0.2311,0.4564,0.7919,0.9354;
0.6068,0.0185,0.9218,0.9169;
0.4859,0.8214,0.7382,0.4102;
0.8912,0.4447,0.1762,0.8936'''))

A = np.array(np.mat('''0.2027,0.2721,0.7467,0.4659;
0.1987,0.1988,0.4450,0.4186;
0.6037,0.0152,0.9318,0.8462'''))
d = np.array([0.0578, 0.3528, 0.8131, 0.0098, 0.1388])
b =  np.array([0.5251, 0.2026, 0.6721])
lb = np.array([-0.1] * 4)
ub = np.array([2] * 4)

ret = lsqlin.lsqlin(C, d, 0, A, b, None, None, \
         lb, ub, None, {'show_progress': False})
print ret['x'].T
# output:
# [-1.00e-01 -1.00e-01  2.15e-01  3.50e-01]
Seems to work fine as well.

For sparse matrices in my case(about hundreds columns, tens thousands rows, 3 non-zero elements in each row) both Matlab and Python provided same solutions, and Python implementation seemed to work a bit faster. However I can not provide any timings or memory consumption test. Just hope it can help somebody someday.

lsqlin.py can be downloaded from here, or just copypasted from below. You can use numpy dense matrices, scipy sparse matrices or cvxopt matrices as inputs. Module is documented:

#!/usr/bin/python

# See http://maggotroot.blogspot.ch/2013/11/constrained-linear-least-squares-in.html for more info
'''
    A simple library to solve constrained linear least squares problems
    with sparse and dense matrices. Uses cvxopt library for 
    optimization
'''

__author__ = 'Valeriy Vishnevskiy'
__email__ = 'valera.vishnevskiy@yandex.ru'
__version__ = '1.0'
__date__ = '22.11.2013'
__license__ = 'WTFPL'

import numpy as np
from cvxopt import solvers, matrix, spmatrix, mul
import itertools
from scipy import sparse


def scipy_sparse_to_spmatrix(A):
    coo = A.tocoo()
    SP = spmatrix(coo.data, coo.row.tolist(), coo.col.tolist())
    return SP

def spmatrix_sparse_to_scipy(A):
    data = np.array(A.V).squeeze()
    rows = np.array(A.I).squeeze()
    cols = np.array(A.J).squeeze()
    return sparse.coo_matrix( (data, (rows, cols)) )

def sparse_None_vstack(A1, A2):
    if A1 is None:
        return A2
    else:
        return sparse.vstack([A1, A2])

def numpy_None_vstack(A1, A2):
    if A1 is None:
        return A2
    else:
        return np.vstack([A1, A2])
        
def numpy_None_concatenate(A1, A2):
    if A1 is None:
        return A2
    else:
        return np.concatenate([A1, A2])

def get_shape(A):
    if isinstance(C, spmatrix):
        return C.size
    else:
        return C.shape

def numpy_to_cvxopt_matrix(A):
    if A is None:
        return A
    if sparse.issparse(A):
        if isinstance(A, sparse.spmatrix):
            return scipy_sparse_to_spmatrix(A)
        else:
            return A
    else:
        if isinstance(A, np.ndarray):
            if A.ndim == 1:
                return matrix(A, (A.shape[0], 1), 'd')
            else:
                return matrix(A, A.shape, 'd')
        else:
            return A

def cvxopt_to_numpy_matrix(A):
    if A is None:
        return A
    if isinstance(A, spmatrix):
        return spmatrix_sparse_to_scipy(A)
    elif isinstance(A, matrix):
        return np.array(A).squeeze()
    else:
        return np.array(A).squeeze()
        

def lsqlin(C, d, reg=0, A=None, b=None, Aeq=None, beq=None, \
        lb=None, ub=None, x0=None, opts=None):
    '''
        Solve linear constrained l2-regularized least squares. Can 
        handle both dense and sparse matrices. Matlab's lsqlin 
        equivalent. It is actually wrapper around CVXOPT QP solver.

            min_x ||C*x  - d||^2_2 + reg * ||x||^2_2
            s.t.  A * x <= b
                  Aeq * x = beq
                  lb <= x <= ub

        Input arguments:
            C   is m x n dense or sparse matrix
            d   is n x 1 dense matrix
            reg is regularization parameter
            A   is p x n dense or sparse matrix
            b   is p x 1 dense matrix
            Aeq is q x n dense or sparse matrix
            beq is q x 1 dense matrix
            lb  is n x 1 matrix or scalar
            ub  is n x 1 matrix or scalar

        Output arguments:
            Return dictionary, the output of CVXOPT QP.

        Dont pass matlab-like empty lists to avoid setting parameters,
        just use None:
            lsqlin(C, d, 0.05, None, None, Aeq, beq) #Correct
            lsqlin(C, d, 0.05, [], [], Aeq, beq) #Wrong!
    '''
    sparse_case = False
    if sparse.issparse(A): #detects both np and cxopt sparse
        sparse_case = True
        #We need A to be scipy sparse, as I couldn't find how 
        #CVXOPT spmatrix can be vstacked
        if isinstance(A, spmatrix):
            A = spmatrix_sparse_to_scipy(A)
            
    C =   numpy_to_cvxopt_matrix(C)
    d =   numpy_to_cvxopt_matrix(d)
    Q = C.T * C
    q = - d.T * C
    nvars = C.size[1]

    if reg > 0:
        if sparse_case:
            I = scipy_sparse_to_spmatrix(sparse.eye(nvars, nvars,\
                                          format='coo'))
        else:
            I = matrix(np.eye(nvars), (nvars, nvars), 'd')
        Q = Q + reg * I

    lb = cvxopt_to_numpy_matrix(lb)
    ub = cvxopt_to_numpy_matrix(ub)
    b  = cvxopt_to_numpy_matrix(b)
    
    if lb is not None:  #Modify 'A' and 'b' to add lb inequalities 
        if lb.size == 1:
            lb = np.repeat(lb, nvars)
    
        if sparse_case:
            lb_A = -sparse.eye(nvars, nvars, format='coo')
            A = sparse_None_vstack(A, lb_A)
        else:
            lb_A = -np.eye(nvars)
            A = numpy_None_vstack(A, lb_A)
        b = numpy_None_concatenate(b, -lb)
    if ub is not None:  #Modify 'A' and 'b' to add ub inequalities
        if ub.size == 1:
            ub = np.repeat(ub, nvars)
        if sparse_case:
            ub_A = sparse.eye(nvars, nvars, format='coo')
            A = sparse_None_vstack(A, ub_A)
        else:
            ub_A = np.eye(nvars)
            A = numpy_None_vstack(A, ub_A)
        b = numpy_None_concatenate(b, ub)

    #Convert data to CVXOPT format
    A =   numpy_to_cvxopt_matrix(A)
    Aeq = numpy_to_cvxopt_matrix(Aeq)
    b =   numpy_to_cvxopt_matrix(b)
    beq = numpy_to_cvxopt_matrix(beq)

    #Set up options
    if opts is not None:
        for k, v in opts.items():
            solvers.options[k] = v
    
    #Run CVXOPT.SQP solver
    sol = solvers.qp(Q, q.T, A, b, Aeq, beq, None, x0)
    return sol

def lsqnonneg(C, d, opts):
    '''
    Solves nonnegative linear least-squares problem:
    
    min_x ||C*x - d||_2^2,  where x >= 0
    '''
    return lsqlin(C, d, reg = 0, A = None, b = None, Aeq = None, \
                 beq = None, lb = 0, ub = None, x0 = None, opts = opts)


if __name__ == '__main__':
    # simple Testing routines
    C = np.array(np.mat('''0.9501,0.7620,0.6153,0.4057;
    0.2311,0.4564,0.7919,0.9354;
    0.6068,0.0185,0.9218,0.9169;
    0.4859,0.8214,0.7382,0.4102;
    0.8912,0.4447,0.1762,0.8936'''))
    sC = sparse.coo_matrix(C)
    csC = scipy_sparse_to_spmatrix(sC)

    A = np.array(np.mat('''0.2027,0.2721,0.7467,0.4659;
    0.1987,0.1988,0.4450,0.4186;
    0.6037,0.0152,0.9318,0.8462'''))
    sA = sparse.coo_matrix(A)
    csA = scipy_sparse_to_spmatrix(sA)

    d = np.array([0.0578, 0.3528, 0.8131, 0.0098, 0.1388])
    md = matrix(d)

    b =  np.array([0.5251, 0.2026, 0.6721])
    mb = matrix(b)

    lb = np.array([-0.1] * 4)
    mlb = matrix(lb)
    mmlb = -0.1

    ub = np.array([2] * 4)
    mub = matrix(ub)
    mmub = 2

    #solvers.options[show_progress'] = False
    opts = {'show_progress': False}

    for iC in [C, sC, csC]:
        for iA in [A, sA, csA]:
            for iD in [d, md]:
                for ilb in [lb, mlb, mmlb]:
                    for iub in [ub, mub, mmub]:
                        for ib in [b, mb]:
                            ret = lsqlin(iC, iD, 0, iA, ib, None, None, ilb, iub, None, opts)
                            print ret['x'].T
    print 'Should be [-1.00e-01 -1.00e-01  2.15e-01  3.50e-01]'
    
    #test lsqnonneg
    C = np.array([[0.0372, 0.2869], [0.6861, 0.7071], [0.6233, 0.6245], [0.6344, 0.6170]]);
    d = np.array([0.8587, 0.1781, 0.0747, 0.8405]);
    ret = lsqnonneg(C, d, {'show_progress': False})
    print ret['x'].T
    print 'Should be [2.5e-07; 6.93e-01]'


10/11/2013

Pairwise distances in Matlab and Octave

It is always rewarding, to spend some time vectorizing computations in matlab: code runs faster, you are happy to see how all these ugly for-cycles roll up and turn into elegant linear algebra formulas, sometimes you even can exploit hidden logic in your computations(see covariance matrix estimation).

Of course you shouldn't be too fanatic about it: code can come less readable, memory consumption can grow unproportionally: see ndgrid example below, or recall some repmat constructions. I would like to have octave and python -style broadcasting implemented in matlab, but they claim, that bsxfun, arrayfun, and JITA loops implementation are fast enough. I strongly disagree.

Anyway. Suppose you want to compute table of pairwise distances for two set of vectors in $d$-dimensional space: $$(P)_{ij} = \|x_i - y_j\|^2_2, \;\; i=1,\dots,n_1,\; j = 1,\dots,n_2, \text{ and } P\in\mathbb{R}^{n_1\times n_2}.$$ We store $x_i$ and $y_i$ as columns in matrices $X$ and $Y$ respectively: $$X = \left[ x_1, \dots, x_{n_1}\right] \in \mathbb{R}^{d\times n_1},$$ $$Y = \left[ y_1, \dots, y_{n_2}\right] \in \mathbb{R}^{d\times n_2}.$$ We can expand $\ell_2$-norm: $$(P)_{ij} = \|x_i - y_j\|^2_2 = x_i^\top x_i + y_j^\top y_j - 2 x_i^\top y_j,$$ now expression for matrix $P$ can be written using matrix multiplications: $$P = a\mathbb{1}_{(n_2\times 1)}^\top + \mathbb{1}_{(n_1\times 1)} b - 2 X^\top Y,$$ where $a$ and $b$ are vectors containing $\ell_2$ norms for every vector $x_i$ and $y_j$: $a_i = \|x_i\|_2^2, \;\; a\in\mathbb{R}^{n_1\times 1}$, and $b_j = \|y_j\|_2^2, \;\; b\in\mathbb{R}^{n_2\times 1}$.
$\mathbb{1}_{(m\times 1)} = \left[1, \dots, 1\right]^\top \, \in \mathbb{R}^{m\times 1}.$
Finally, this formula can be written in vectorized matlab expression:

a = sum(X.^2, 1);
b = sum(Y.^2, 1);
P = a' * ones(1, N2)  + ones(N1, 1) * b - 2 * (X' * Y); 

This is quite fast and elegant implementation, that has quite the same(can be twice faster, can be twice slower) running time, as matlabs built-in pdist2. And it is much faster and more memory efficient, than ndgrid-based(see below) pairwise distance computation. Take a look at code, comparing 3 different implementations for this problem. Notation is consistent with formulas above.

%generate data
d = 100; % dimensionality
N1 = 500; % number of vectors
N2 = 400; % number of vectors
X = rand(d, N1);
Y = rand(d, N2);

%built-in
tic();
pd = pdist2(X', Y');
fprintf('"built-in":\t%.5f seconds\n', toc());

%ndgrid
tic();
[gx, gy] = ndgrid(1 : N1, 1 : N2);
P_grid = reshape(sqrt(sum( (X(:, gx) - Y(:, gy)).^2, 1)), [N1, N2]);
t = toc();
fprintf('"ndgrid":\t%.5f seconds. Max err: %e\n', t, max(max(abs(P_grid - pd))));

%matrix
tic();
a = sum(X.^2, 1);
b = sum(Y.^2, 1);
%I use abs to get rid of small(1e-15) negative diagoanl values, 
%produced by computational errors
P =  abs(a' * ones(1, N2)  + ones(N1, 1) * b - 2 * (X' * Y)); 
P = sqrt(P); %to be consistent with pdist2
t = toc();
fprintf('"matrix":\t%.5f seconds. Max err: %e\n', t, max(max(abs(P - pd))));
Output on my machine:
"built-in": 0.02721 seconds
"ndgrid": 0.67575 seconds. Max err: 0.000000e+00
"matrix": 0.01662 seconds. Max err: 1.243450e-14

2/14/2010

Летняя поездка в Les Deux Alpes с велосипедами и лыжами





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

Итак, напомню, что главной целью было покатать даунхилл на великах в горах. Я считал, что такая поездка обойдется недешево: иначе почему я не знаю почти никого, кто катался на байке в европе? Но оказалось, что по деньгам все может выйти дешевле обычной зимней поездки в альпы на лыжах. Вообще, почти каждый европейский курорт предлагает летом катание на велосипеде. Это и Ле Дез Альпс, и Ле Же, Майрхофен... Знаю только, что Шамони принципиально на это не идет -- все же приходится портить ландшафт зимних трасс. Уже не помню почему, но мой выбор пал на франуцузский поселочек Les Deux Alpes(далее л2а). Видимо из-за того, что там есть и даунхильные трассы, и байк-парк, и ледник со снежным парком. Летнее катание на леднике уже есть далеко не везде, знаю пару мест в Норвегии, Австрии, например, ледник Hintertux, что недалеко от Майрхофена.
Вот с какими проблемами мы столкнулись: перелет из Москвы с велосипедом и лыжами, трансфер из аэропорта в поселок, бронирование апартаментов. Ехали мы втроем на 2 недели: конец июля начало августа. Обсудим все по порядку.

Собираем вещи
Сперва надо было придумать как везти весь инвентарь с собой. Ну как обычно: вещи в чемодан, лыжи в соответствующий чехол, а вот велосипед... Можно купить велосумку, но они либо не приспособлены для транспортировки даунхильного байка, либо стоят нехилых денег. Просто так обмотать тряпкой раму нельзя. Можно использовать коробки, в которых сами велосипеды доставляют по магазинам(гоу в соколя, попросите коробку в магазине, или просто покопайтесь на свалке), но мне больше подошла коробка от плазменного телика. С байка были сняты: оба колеса, амортизатор, вилка, тормоза, переклюк, педали(это обязательно требование авиакомпаний). Также в коробку были кинуты всякие запасные детали, инструменты. В итоге коробка весила ~35 кг, лыжный чехол ~15 кг, чемодан ~25 кг. Примерно 75 кило... И кто нас довезет?

Летим из Москвы
Так как никакие турфирмы не занимаются поездками на велосипедах, приходилось смотреть, какие авиалинии наиболее лояльно относящиеся к спортинвентарю, могли нас закинуть поближе к л2а.
  • Аэрофлот шел нааахуй сразу: сам билет не дешевый и ограничение 20 кило, если бы мы платили за каждый лишний килограмм, то мне билет стоил бы тысяч 60. Однако возможны варианты, если у вас есть всякие аэрофлотовские бонусы и вы возьмете бизнес-класс, где ограничение 30 кг. Смотрите тут http://wwww.aeroflot.ru/templates/before_and_after_fly/sverh_bagazh.html http://wwww.aeroflot.ru/templates/before_and_after_fly/sverh_bagazh.html
  • Air France. Не выгоден из-за того, что у нас были лыжи. Пришлось бы платить 240 евро в каждую сторону (80 лыжи + 2х80 за байк). http://www.airfrance.com/RU/en/common/guidevoyageur/pratique/bagage_equipement_sport_airfrance.htm
  • Swiss Air. Ими то мы и полетели в Женеву. Лыжи можно всегда провозить бесплатно, велик стоит 70 евро в каждую сторону(на сайте написано что он должен быть не тяжелее 23 кг, но они на это не смотрели). + был дешевый билет: мы взяли за 14000, но если бы подсуетились на неделю раньше, то были билеты где-то за 9000. Ну и каг бы свисс эир несравним с говнофлотом. И я просто не могу не отметить, что они дают железные вилки и ножи и халявное вино. Сразу видно, что пацаны не очкуют: чего стоят запреты на минареты.
Едем из аэропорта в горы




Если вы посмотрите на карту, то увидите, что из Женевы до л2а 212 километров. Рейс был вечерний и мы прилетали где-то в 20:00. Поезда и автобусы уже не ходили. Так что было 3 варианта:
  • Ночевать в Женеве, а потом на поездах, автобусах, через Гренобль ехать в л2а. Но сама ночь в Женеве стоила денег, нам надо было со всеми коробками доехать до отеля, а потом как то с таким грузом ехать на поезде, а поезда по Европе не особо дешевые. Да еще терялся день катание и все это жутко выматывало. Так что не вариант.
  • Взять машину на прокат в Женеве, довезти вещи до л2а, а потом сдать ее в Гренобле. Но нам не было 22-х лет, а следовательно, мы не могли забронировать машину, на которой могли поехать 3 человека, 3 чемодана, 2 велика, лыжи и борд)). Да и сам прокат машины в Швейцарии заметно дороже, чем во франции. Ну и сам факт бронирования большого автомобиля из Москве не очень то гарантирует, что по прилету вас будет ждать машина похожего класса. Тоже не вариант.
  • Заказать такси(в нашем случае микроавтобус). Это стоило 470 евро. Да, конечно не дешево платить по 300 евро за трансфер(470 туда и столько же обратно), но это оказался оптимальный для нас вариант.
На самом деле, это были два самых сложных вопроса, которые нам надо было решить. Остальное было просто

Проживание, виза, подъемники
Бронируете по кредитке апартаменты\отель через официальные сайты ( http://www.les2alpes.com/ http://2alpes.com/ ). Вообще, проживание летом на порядок дешевле, чем зимой. Мы взяли 4-х местные апартаменты(Cote Brune) на 2 недели за 600 евро и жили в трехстах метрах от подъемника. (Но втроем в них было уже чуток тесно -- ох эти французы... ). Связываетесь с ними по мейлу\телефону, вам высылают приглашение. С приглашением и билетом на самолет идете в консульство и получаете визу. В нашем случае нам дали визу за 2 дня на 1 год. Покупаете в какой-нибудь турфирме страховку(берите самую дорогую, это где-то 30 евро).
Подъемники мы брали каждый день отдельно: нам нужен был и велосипедный и лыжный скипас, но далеко не каждый день мы ездили кататься на лыжах. Расценки на подъемы можно посмотреть на официальном сайте курорта.

Давайте примерно прикинем стоимость такой двухнедельной поездки:
12000 р перелет +
140 евро за перевес +
300 евро трансфер +
15-30 евро в день на подъемник (в зависимости от того весь ли день вы катаетесь, едите ли на ледник)+
15 евро в день на еду (это уже все индивидуально, недавно я ездил в Альпы и некоторые тратили по 5 евро в день на еду)+
200 евро за проживание.
Получается примерно 60000 с человека. Но не забывайте, что можно сэкономить на еде, перелете, или трансфере (например лететь эир фрэнсом до Леона или Гренобля). Вообще имеет смысл поехать в Les Gets: это совсем не далеко от Женевы, сами трассы там вроде бы более качественные, но нету ледника, и перепад высот меньше.

Даже не знаю, стоит ли рассказывать про впечатления от поездки. Они непередаваемы. Минус был только один: огромные очереди на подъемнике в сноупарк, но об это позже.
В л2а есть все: даунхилл с перепадом 2500 метров, кросскантри, байк-парк, сноупарк, банджи-джампинг с огромнейшей высоты, горные озера у велосипедных трасс, гонрое озеро с пляжем в самом поселке, бассейн, скалы по близости и стена для скалолазания в самом поселке, там есть каток, горная речка с рафтингом, парк развлечений, параглайдинг, батуты, скейтпарк, гольф, стрельба из лука, маленький халфпайп и батут почти у каждого отеля, можно погулять по горам, в конце концов... И все это в маленьком поселочке Les Deux Alpes на высоте 1600 метров, который вытянулся в длину на 2 километра и в ширину не более 400 метров.





Про катание на байке
Актуальную карту склонов можно посмотреть на официальном сайте. Кабинка закидывает нас к подножье ледника на высоту 2800 метров. Откуда, первые метров 600 по вертикали нас ждут каменистые хардкорные трассы, напоминающие Марс или Луну. Далее появляется травка и деревья. Трассы становятся грунтовыми, на них появляется жутко неприятная тормозная гребенка перед поворотами. Еще чуть ниже появляются деревья и норт-шоры. При спуске в поселок нас встречает кучей дропов( от 1 до 10 метров) байк-парк. Спустившись в поселок можно поехать вниз до Веноска по одной из самых запоминающихся трасс с вертикальным перепадом 900 метров: это достаточно сложно сделать на одном дыхании))




Хочется отметить, что классификация даунхильных и лыжных трасс по цветам имеет мало общего: по красной трассе уже просто сложно съехать, по черной трассе я только 1 раз смог съехать без падений и долгих остановок на отдых))) Несколько советов: запаска, инструменты, шестигранник всегда при себе, мягко настройте вилку, одевайте всю защиту. Я редко там видел людей без шлема(вообще не видел), полной голени и полного бодиармора. Кстати, прокат байка с защитой стоит от 20(приличный подвес подвес) до 50(топовая кона стинки\сантакруз) евро за весь день, можно брать на пол дня.






Про катание в парке на леднике
Подъемник начинает работать с 7 утра, все закрывается где-то в 14:00. Но прыгать нормальные серии становится сложно где-то к 13:00. Если ночью было холодно, то иногда приходится ждать часов до 10, пока приземы растают от льда. Но в общем-то ходов хватало и приземы становились очень мягкими. И все было бы просто супер, если бы не было кууучи итальянцев и испанцев, приехавших в бордические лагеря. Они устраивали очереди на подъемниках(10-15 минут надо было ждать каждый подъем), и их "про" постоянно не докручивали 540 на борде и стесывали кантами приземления больших кикеров до льда.
Вообще там была фан серия, состоящая из джибинга и мелких недокикеров, 2 огромных пайпа, 1 огромная пирамида. Паблик лайн: 12 и 15 метров кикеры первая полка, 8 и 10 метров вторая полка. Про лайн: 18 и 22 метра первая полка, 10 и 15 метров вторая полка. К концу поездки паблик лайн тупо снесли: перестало хватать ходов. Самый вменяемый трамплин -- это 22-х метровый бигэир: его тщательнее всего шейпили.



Еще видео и фото катания на байке в л2а можно найти на http://pinkbike.com по тегам 2alpes, deux alpes...