2016-05-30

Sơ lược Log-Likelihood (LL) và Maximum-Likelihood Estimation (MLE)

Sơ lược về LL, MLE

Log-Likelihood

Likelihood function hay đơn giản gọi là likelihood là một hàm tham số trong thống kê.
Bình thường cách dùng của từ khả năng likelihood (đôi khi còn được dịch khả dĩ?) có nghĩa gần giống từ xác xuất probability. Tuy nhiên khi sử dụng trong thống kê học, cách dùng phụ thuộc vào vai trò của kết quả hay thông số.

Xác xuất probability được sử dụng khi mô tả một hàm của kết quả đầu ra (outcome) với một tham số xác định (fixed parameter value) \(P\left( {x|\theta } \right)\). Giả sử có một biến ngẫu nhiên X tuân theo phân phối tham số (parameterized distribution) \(f\left( {x;\theta } \right)\) (ví dụ phân với chuẩn \(f(x;\mu ,\sigma )\) thì \(\theta \)\(\mu ,\sigma \)) với \(\theta \) là tham số của phân phối f.

Khi đó xác xuất X = x sẽ là \(P\left( {X = x} \right) = f\left( {x;{\rm{ }}\theta } \right)\) với \(\theta \) đã biết. Nói cách khác cho giá trị cụ thể \(\theta \), \(P\left( {x|\theta } \right)\) là xác xuất sẽ quan sát thấy kết quả outcome đại diện bởi x (x mô tả outcome).

Ví dụ: tung đồng xu 10 lần, đồng xu cân đối (công bằng), xác xuất xuất hiện mặt ngửa x lần là bao nhiêu?

Ví dụ biết xác xuất xuất hiện một mặt của đồng xu cân đối tuân theo phân phối nhị thức với p = 0.5. Xác xuất xuất hiện mặt ngửa 7 lần x = 7
\(P\left( {X = x} \right) = f\left( {x;n,\;p} \right) = B\left( {n,p} \right) = \left( {\begin{array}{*{20}{c}}n\\x\end{array}} \right){p^x}{\left( {1 - p} \right)^{n - x}} = \left( {\begin{array}{*{20}{c}}n\\x\end{array}} \right){0.5^x}{\left( {1 - 0.5} \right)^{n - x}}\)
với x = 7, n = 10, p = 0.5 thì \(\;f\left( {x;n,\;p} \right) = 0.117\)

Trong khi đó likelihood được sử dụng khi mô tả một hàm của tham số (parameter) cho ra một kết quả outcome (outcome đã biết).

Ví dụ tung đồng xu 10 lần và ngửa 7 lần vậy đồng xu có cân đối? Trong thực tế đây là trường hợp thường xảy ra khi mô hình hóa một quá trình ngẫu nhiên (real life stochastic process) khi không biết \(\theta \) mà chỉ có thể quan sát x. Do đó cần ước tính \(\theta \) để giá trị này phù hợp với giá trị quan sát x.

Trong trường hợp ngược lại này giá trị của p là chưa xác định likelihood function sẽ viết như sau:
\(L\left( {x;n,\;p} \right) = \left( {\begin{array}{*{20}{c}}n\\x\end{array}} \right){p^x}{\left( {1 - p} \right)^{n - x}}\)

Likelihood sẽ là tập hợp của các parameter thỏa mãn các kết quả đầu ra quan sát được và bằng đúng xác xuất xảy ra của những quan sát này. Theo đó hàm likelihood được viết gần như hàm xác xuất với lưu ý là \(\theta \) chưa biết và X = x đã biết.
\(L\left( {\theta |x} \right) = P(x|\theta )\)

Likelihood-ratio test là một kiểm định thống kê dùng kiểm tra goodness of fit (GoF) của hai mô hình trong đó một mô hình là null-model (trường hợp đặc biệt) còn được viết tắt là -2LL (-2 log-likelihood). Kiểm định này sử dụng tỷ số likelihood (likelihood ratio).

Null-hypothesis \({H_0}:\rho  = {\rm{ }}0.50\)
Alternate hypothesis \({H_a}:\rho  \ne {\rm{ }}0.50\)

Likelihood-ratio test dựa trên tỷ số likelihood (likelihood rate) được  bởi ký tự capital lambda
\({\rm{ }}\Lambda {\rm{ }}\left( x \right) = \frac{{L({\theta _0}|x)}}{{L({\theta _a}|x)}} = \frac{{f\left( {x|{\theta _0}} \right)}}{{f(x|{\theta _a})}}\)
Likelihood-ratio test trả lời câu hỏi liệu dữ liệu (data) có ý nghĩa thống kê cho thấy ít có khả năng xảy ra giả thuyết null hơn giả thuyết đối bằng cách tính log-likelihood giữa giả thuyết null và giả thuyết đối và xem xét độ khác nhau giữa hai giá trị này (note \(\log \frac{a}{b} = \log a - \log b\)):
\(D{\rm{ }} = {\rm{ }}2{\rm{ }}\left( {L{L_a} - {\rm{ }}L{L_0}} \right)\)

Việc nhân giá trị \(L{L_a} - {\rm{ }}L{L_0}\) với 2 là một kỹ thuật thống kê với mục đích làm cho giá trị D có phân phối \({\chi ^2}\).

Ví dụ log-likelihood function của một phân phối chuẩn có dạng như sau:
\(l\left( {\mu ,\sigma } \right) =  - n\log \left( {2\pi {\sigma ^2}} \right) - \frac{{\mathop \sum \nolimits_i {{\left( {{X_i} - \mu } \right)}^2}}}{{2{\sigma ^2}}}\)

Maximum-Likelihood Estimation

Ước lượng hợp lý cực đại hay còn dịch là ước lượng khả năng cực đại Maximum-Likelihood Estimation (MLE) là một kỹ thuật trong thống kê dùng để ước lượng giá trị tham số của một mô hình xác suất dựa trên những dữ liệu có được. Phương pháp này được nhà toán học R. A. Fisher phát triển vào khoảng 1912-1922.

MLE dựa trên giả thiết rằng các mẫu dữ liệu \(\;D = \left\{ {{X_{1,}} \ldots ,{X_N}} \right\}\) có được đều độc lập và có cùng phân bố (i.i.d–independent and identically distributed), với hàm phân bố thuộc một lớp cụ thể (ví dụ như Gaussian hoặc luỹ thừa) với tham số \(\theta \) chưa biết. Mục tiêu của MLE là đi tìm giá trị của tham số để tối ưu hoá hàm thiệt hại (loss function).

Trong trường hợp của MLE, hàm thiệt hại được định nghĩa là hàm logarithm của hàm khả năng (likelihood function) \(\ln (P(D|\theta ))\)

Theo giả thiết các mẫu dữ liệu là i.i.d ta có hàm khả năng
\(P\left( {D{\rm{|}}\theta } \right) = P\left( {{X_1}, \ldots ,{X_N}{\rm{|}}\theta } \right) = \mathop \prod \limits_{i = 1}^N P({X_i}|\theta )\)
do đó khi lấy logarithm loss function có giá trị
\(\ln (P(D|\theta )) = \mathop \sum \limits_{i = 1}^N \ln (P({X_i}|\theta ))\)

Tham số của mô hình dựa sẽ được ước lượng bằng các cách gán với các giá trị sao cho hàm số trên giá trị đạt cực đại:

\(\left\{ {{{\hat \theta }_{mle}}} \right\} \subseteq \left\{ {{\rm{argma}}{{\rm{x}}_{\theta  \in {\rm{\Theta }}}}{\rm{ln}}(P(D|\theta ))} \right\}\)

2016-05-24

Gogo's Adventure with English (dành cho con 3-4 tuổi)



Giới thiệu thì đây là chương trình dành cho trẻ từ 3 - 9 tuổi, mình thấy khá ổn. Download về xong bật TV cho Gon và Pon xem sẵn học tiếng Anh luôn. Ăn xong toàn Cartoon Network hoài cũng ko được. Nếu ko chú ý chất lượng thì xem trên Youtube cũng dc. Có list thì chỉ có 29 lessons thôi. 
List này thì tới 39 lessons Learning Enlish with GOGO



Đầy đủ thì chịu khó search link iso, mình ko post link lên đâ, nhưng tìm cũng dễ như trên Fshare cũng có. Không thì xem từng bài search trên Youtube với tên bài như bên dưới.

Gogo's Adventures with English 1 

Gogo's Adventures with English 2 

Gogo's Adventures with English 3


Gogo's Adventures with English 4


Gogo's Adventures with English 5 

2016-05-19

Using jQuery DataTables with ASP.NET and NHibernate, QueryOver


Mình viết lại cái server-side data của DataTables 1.10+ cho ASP.NET MVC.

Đã có rất nhiều trên NuGet để có thể ngó qua tham khảo. Ví dụ:
https://github.com/ALMMa/datatables.aspnet
https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables

Nhưng mình thấy rằng cũng ko cần support nhiều thứ như vậy, chủ yếu là ModelBinder để request thôi, còn lại putput JSON thì tự do. Cho nên modify lại chút, lấy những cái cần xài thôi.

Việc còn lại là query data theo cái DataTablesRequest thì dùng QueryOver là tiện nhất. Nếu cần biết về QueryOver thì series của Andrew Whitaker là nên đọc
http://blog.andrewawhitaker.com/blog/2014/03/12/queryover-series-part-1-why-queryover/

Request

OK code thôi. Lười không tạo project trên GitHub nên post code thẳng lên đây. Giả sử code query Project, nếu cần thì có thể refactor đoạn này thành DataTables<T>Đầu tiên DataTablesRequest là request gửi AJAX lên và đã parse bởi ModelBinder. Cái này sẽ nói sau. Request có các thông tin:

columns[i][data]    
columns[i][name]    
columns[i][orderable]
columns[i][search][regex]
columns[i][search][value]   
columns[i][searchable]  
...     
draw    
length  
order[i][column]    
order[i][dir]
... 
search[regex]
search[value]   
start

Column name

Mình điều chỉ lại column name cần lấy từ column data và column name trong request gửi lên. Nếu không name mà có data thì xài Inflector chuyển dạng Pascal case ví dụ start_date thành StartDate.
Nếu có column data nhưng là số thường là những column như column số thứ tự, column action (Edit, View, Delete) thì bỏ qua.

Sorting

Dùng 

query.UnderlyingCriteria.AddOrder(new Order(column.Name, sort.Direction == 1));

Searching

Với search không phải regular expression thì dùng Restrictions

query.Where(Restrictions.InsensitiveLike(column.Name, val, MatchMode.Anywhere));

Trường hợp regular expression có thể viết thêm extension cho MS SQL Server, tạm thời không giải quyết ở đây.

Select list

Để select các column sẽ dùng SelectList(), tuy nhiên cần phần AliasToBean nên phải dùng WithAlias. Còn nếu không cần thì mình có thể dùng ngay IList<object>

public static class MemberExpressionBuilder
{
    public static Expression<Func<T>> Create<TModel, T>(string propertyName)
    {
        var propertyInfo = typeof(TModel).GetProperty(propertyName);

        var entityParam = Expression.Parameter(typeof(TModel), "e");
        Expression columnExpr = Expression.Property(entityParam, propertyInfo);

        if (propertyInfo.PropertyType != typeof(T))
        {
            columnExpr = Expression.Convert(columnExpr, typeof(T));
        }

        return Expression.Lambda<Func<T>>(columnExpr);
    }
}

Sau khi có data xong thì transform data theo JSON property name yêu cầu. Dùng JsonProperty cho property như draw, recordsTotal, recordsFiltered...

Code của controller

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/// <summary>
/// GET: /Projects/DataTable
/// </summary>
[HttpPost]
[Route("Projects/DataTables")]
public ActionResult DataTables(DataTablesRequest filterRequest)
{
 //if (!Request.IsAjaxRequest())
 //{
 //    ViewBag.Message = "Not Supported";
 //    return View();
 //}

 var query = NHibernateSession.Current.QueryOver<Project>();

 foreach (var column in filterRequest.Columns.Values)
 {

  if (string.IsNullOrEmpty(column.Name) &&
   !string.IsNullOrEmpty(column.Data))
  {
   // If data is number, maybe it is compute column?
   int i;
   if (int.TryParse(column.Data, out i))
   {
    continue;
   }

   // Use reflector from data
   column.Name = column.Data.Pascalize();
  }
 }

 // Order by
 if (filterRequest.Sort.Count > 0)
 {
  foreach (var sort in filterRequest.Sort)
  {
   var column = filterRequest.Columns[sort.Column];
   if (!column.Sortable ||
    string.IsNullOrEmpty(column.Name) ||
    string.IsNullOrEmpty(column.Data))
   {
    continue;
   }

   query.UnderlyingCriteria.AddOrder(
    new Order(column.Name, sort.Direction == 1));
  }
 }

 // Apply search
 if (filterRequest.Search != null &&
  !string.IsNullOrWhiteSpace(filterRequest.Search.Value))
 {
  foreach (var column in filterRequest.Columns.Values)
  {
   if (!column.Searchable ||
    string.IsNullOrEmpty(column.Name) ||
    string.IsNullOrEmpty(column.Data))
   {
    continue;
   }

   var val = filterRequest.Search.Value;
   if (filterRequest.Search.IsRegex)
   {

   }
   else
   {
    query.Where(Restrictions.InsensitiveLike(
     column.Name, val, MatchMode.Anywhere));
   }
  }
 }

 var countQuery = query.ToRowCountQuery();
 var totalCount = countQuery.FutureValue<int>();

 Project project = null;
 query.SelectList(
  list =>
  {
   list = list.Select(x => x.Id).WithAlias(() => project.Id);

   foreach (var column in filterRequest.Columns.Values)
   {
    if (string.IsNullOrEmpty(column.Name) ||
     string.IsNullOrEmpty(column.Data))
    {
     continue;
    }

    list = list.Select(Projections.Property(column.Name))
     .WithAlias(MemberExpressionBuilder
      .Create<Project, object>(column.Name));
   }

   return list;
  }
 ).TransformUsing(Transformers.AliasToBean<Project>())
  .Skip(filterRequest.Start);

 if (filterRequest.Length > 0)
 {
  query.Take(filterRequest.Length);
 }

 var results = query.List<Project>() ?? new List<Project>();

 var dataSource = results.ToArray();
 var response = new DataTablesResponse<Project>(filterRequest.Draw, totalCount.Value,
  dataSource.Length, dataSource);

 var transformResponse = response.Transform<Project, object>(x =>
 {
  var row = new Dictionary<string, object>();

  row.Add("id", x.Id);
  foreach (var column in filterRequest.Columns.Values)
  {
   if (string.IsNullOrEmpty(column.Name) ||
    string.IsNullOrEmpty(column.Data))
   {
    continue;
   }

   // Get property value
   var propertyInfo = typeof(Project).GetProperty(column.Name);
   object propertyValue = null;
   if (propertyInfo != null)
   {
    propertyValue = propertyInfo.GetValue(x, null);
   }

   if (!row.ContainsKey(column.Data))
   {
    row.Add(column.Data, propertyValue);
   }
  }

  return row;
 });

 string serialized = JsonConvert.SerializeObject(
  transformResponse, Formatting.Indented);

 Response.ContentEncoding = Encoding.UTF8;
 string contentType = "application/json; charset=utf-8";

 var bytes = Encoding.UTF8.GetBytes(serialized);
 return new FileContentResult(bytes, contentType);
}

DataTables request

Các interface class dùng parse request.

IColumn

/// <summary>
/// Columns' parameters for filter requests
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public interface IColumn
{
 /// <summary>
 /// Column's data source.
 /// </summary>
 string Data { get; set; }

 /// <summary>
 /// Column's name.
 /// </summary>
 string Name { get; set; }

 /// <summary>
 /// Flag to indicate if this column is searchable (true) or not (false). 
 /// </summary>
 bool Searchable { get; set; }

 /// <summary>
 /// Flag to indicate if this column is sortable (true) or not (false).
 /// </summary>
 bool Sortable { get; set; }

 /// <summary>
 /// The search component for the column.
 /// </summary>
 ISearch Search { get; set; }
}


ISearch

/// <summary>
/// Search configuration for columns of a filter request
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public interface ISearch
{
 /// <summary>
 /// Search value to apply to this specific column.
 /// </summary>
 string Value { get; set; }

 /// <summary>
 /// Flag to indicate if the search term for this column should be treated as regular expression (true) or not (false). As with global search, normally server-side processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible and at the discretion of your script.
 /// </summary>
 bool IsRegex { get; set; }
}


ISort

/// <summary>
/// Sort configuration for filter requests
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public interface ISort
{
 /// <summary>
 /// Column to which ordering should be applied. This is an index 
 /// reference to the columns array of information that is also submitted to the server.
 /// </summary>
 int Column { get; set; }

 /// <summary>
 /// Ordering direction for this column. It will be ASC or DESC to 
 /// indicate ascending ordering or descending ordering, respectively.
 /// </summary>
 int Direction { get; set; }
}


IDataTablesRequest

/// <summary>
/// Filter request parameters
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public interface IDataTablesRequest
{
 /// <summary>
 /// Draw counter. This is used by DataTables to ensure that the AJAX 
 /// returns from server-side processing requests are drawn in sequence by DataTables 
 /// (AJAX requests are asynchronous and thus can return out of sequence). 
 /// </summary>
 int Draw { get; set; }

 /// <summary>
 /// Paging first record indicator. This is the start point in the current data set 
 /// (0 index based - i.e. 0 is the first record).
 /// </summary>
 int Start { get; set; }

 /// <summary>
 /// Number of records that the table can display in the current draw. 
 /// It is expected that the number of records returned will be equal to 
 /// this number, unless the server has fewer records to return. 
 /// Note that this can be -1 to indicate that all records should be returned 
 /// (although that negates any benefits of server-side processing!)
 /// </summary>
 int Length { get; set; }

 /// <summary>
 /// Global search component to be applied to all columns which have searchable as true.
 /// </summary>
 ISearch Search { get; set; }

 /// <summary>
 /// The columns' sorting configuration.
 /// </summary>
 List<ISort> Sort { get; set; }

 /// <summary>
 /// The columns collection.
 /// </summary>
 Dictionary<int, IColumn> Columns { get; set; }
}


IDataTablesResponse

/// <summary>
/// Mutable page response
/// 
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public interface IDataTablesResponse
{
 /// <summary>
 /// The draw counter that this object is a response to - 
 /// from the draw parameter sent as part of the data request.
 /// </summary>
 int Draw { get; set; }

 /// <summary>
 /// Total records, before filtering (i.e. the total number of records in the database).
 /// </summary>
 int TotalRecords { get; set; }

 /// <summary>
 /// Total records, after filtering (i.e. the total number of records 
 /// after filtering has been applied - not just the number of records being returned for this page of data).
 /// </summary>
 int TotalFilteredRecords { get; set; }

 /// <summary>
 /// The data to be displayed in the table. This is an array of data 
 /// source objects, one for each row, which will be used by DataTables. 
 /// Note that this parameter's name can be changed using the ajaxDT option's dataSrc property.
 /// </summary>
 object[] Data { get; set; }

 /// <summary>
 /// Optional: If an error occurs during the running of 
 /// the server-side processing script, you can inform the user of 
 /// this error by passing back the error message to be displayed using this parameter. 
 /// Do not include if there is no error.
 /// </summary>
 string Error { get; set; }
}

/// <summary>
/// Type-safe page response
/// </summary>
/// <typeparam name="TSource">The type of the data array</typeparam>
public interface IDataTablesResponse<TSource> : IDataTablesResponse
{
 TSource[] DataSource { get; set; }

 IDataTablesResponse<TSource> Transform(Func<TSource, TSource> rowTransform);

 IDataTablesResponse<object> Transform<TMutableSource, TTransform>(
  Func<TMutableSource, TTransform> rowTransform);
}


FormConvertHelper

Dùng parse optional parameter cho DataTablesRequest, không cần thì có thể bỏ luôn.

/// <summary>
/// Form convert helper
/// 
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public static class FormConvertHelper
{
 public static void ReadForm(object message, Dictionary<string, object> otherValues, 
  Func<PropertyDescriptor, bool> shouldBypass)
 {
  var objectType = message.GetType();
  var typeDescriptor = TypeDescriptor.GetProvider(objectType).GetTypeDescriptor(objectType);

  foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.GetProperties())
  {
   if (shouldBypass != null && shouldBypass(propertyDescriptor))
   {
    continue;
   }

   if (propertyDescriptor.Attributes.Cast<Attribute>()
    .OfType<JsonIgnoreAttribute>().Count() == 0)
   {
    var propAttr = propertyDescriptor.Attributes.Cast<Attribute>()
     .OfType<JsonPropertyAttribute>().SingleOrDefault();
    var propertyName = propAttr != null ? 
     propAttr.PropertyName : propertyDescriptor.Name;

    if (otherValues.ContainsKey(propertyName))
    {
     if (otherValues[propertyName] != null)
     {
      var convertedValue = TypeHelper.GetDefaultValue(propertyDescriptor.PropertyType);
      var success = TypeHelper.TryCast(otherValues[propertyName], out convertedValue, propertyDescriptor.PropertyType);
      if (success)
      {
       propertyDescriptor.SetValue(message, convertedValue);
      }
     }
    }
   }
  }
 }

 public static void ReadForm(object message, IValueProvider valueProvider, 
  Func<PropertyDescriptor, bool> shouldBypass)
 {
  var objectType = message.GetType();
  var typeDescriptor = TypeDescriptor.GetProvider(objectType).GetTypeDescriptor(objectType);

  foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.GetProperties())
  {
   if (shouldBypass != null && shouldBypass(propertyDescriptor))
   {
    continue;
   }

   if (propertyDescriptor.Attributes.Cast<Attribute>().OfType<JsonIgnoreAttribute>().Count() == 0)
   {
    var propAttr = propertyDescriptor.Attributes
     .Cast<Attribute>().OfType<JsonPropertyAttribute>().SingleOrDefault();
    var propertyName = propAttr != null ? propAttr.PropertyName : propertyDescriptor.Name;

    var valueResult = valueProvider.GetValue(propertyName);
    if (valueResult != null)
    {
     object convertedValue = valueResult.ConvertTo(propertyDescriptor.PropertyType);
     propertyDescriptor.SetValue(message, convertedValue);
    }
   }
  }
 }

 public static IEnumerable<PropertyDescriptor> GetPropertiesFromType(Type type)
 {
  var typeDescriptor = TypeDescriptor.GetProvider(type).GetTypeDescriptor(type);
  foreach (PropertyDescriptor propertyDescriptor in typeDescriptor.GetProperties())
  {
   yield return propertyDescriptor;
  }
 }
}


TypeHelper

/// <summary>
/// Type helper
/// 
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public static class TypeHelper
{
 public static bool CanAssignValue(this PropertyDescriptor p, object value)
 {
  return value == null ? p.IsNullable() : p.PropertyType.IsInstanceOfType(value);
 }

 public static bool IsNullable(this PropertyDescriptor p)
 {
  return p.PropertyType.IsNullable();
 }

 public static bool IsNullable(this Type t)
 {
  return !t.IsValueType || Nullable.GetUnderlyingType(t) != null;
 }

 public static T GetDefaultValue<T>()
 {
  // We want an Func<T> which returns the default.
  // Create that expression here.
  var e = Expression.Lambda<Func<T>>(
   // The default value, always get what the *code* tells us.
   Expression.Default(typeof(T))
  );

  // Compile and return the value.
  return e.Compile()();
 }

 public static object GetDefaultValue(Type type)
 {
  // Validate parameters.
  if (type == null)
  {
   throw new ArgumentNullException("type");
  }

  // We want an Func<object> which returns the default.
  // Create that expression here.
  var e = Expression.Lambda<Func<object>>(
   // Have to convert to object.
   Expression.Convert(
   // The default value, always get what the *code* tells us.
    Expression.Default(type), typeof(object)
   )
  );

  // Compile and return the value.
  return e.Compile()();
 }

 public static bool TryCast<T>(object value, out T result)
 {
  var type = typeof(T);

  // If the type is nullable and the result should be null, set a null value.
  if (type.IsNullable() && (value == null || value == DBNull.Value))
  {
   result = default(T);
   return true;
  }

  // Convert.ChangeType fails on Nullable<T> types.  We want to try to cast to the underlying type anyway.
  var underlyingType = Nullable.GetUnderlyingType(type) ?? type;

  try
  {
   if (underlyingType == typeof(Guid))
   {
    if (value is string)
    {
     value = new Guid(value as string);
    }
    if (value is byte[])
    {
     value = new Guid(value as byte[]);
    }
   }
   else if (underlyingType.IsEnum && value != null)
   {
    value = Enum.Parse(underlyingType, value.ToString(), true);
   }

   result = (T)Convert.ChangeType(value, underlyingType);
   return true;
  }
  catch
  {
   result = default(T);
   return false;
  }
 }

 public static bool TryCast(object value, out object result, Type type)
 {
  var s = "TryCast";
  var openTryCastMethod = typeof(TypeHelper)
   .GetMethods().Single(x => x.Name == s && x.GetGenericArguments().Count() == 1);
  var closedTryCastMethod = openTryCastMethod.MakeGenericMethod(type);

  result = TypeHelper.GetDefaultValue(type);
  var args = new object[] { value, result };
  bool success = (bool)closedTryCastMethod.Invoke(null, args);
  result = args[1];

  return success;
 }
}


Column

/// <summary>
/// Datatables column
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public class Column : IColumn
{
 /// <summary>
 /// Column's data source.
 /// </summary>
 public virtual string Data { get; set; }

 /// <summary>
 /// Column's name.
 /// </summary>
 public virtual string Name { get; set; }

 /// <summary>
 /// Flag to indicate if this column is searchable (true) or not (false). 
 /// </summary>
 public virtual bool Searchable { get; set; }

 /// <summary>
 /// Flag to indicate if this column is sortable (true) or not (false).
 /// </summary>
 public virtual bool Sortable { get; set; }

 /// <summary>
 /// The search component for the column.
 /// </summary>
 private ISearch _search;

 public virtual ISearch Search
 {
  get
  {
   if (_search == null)
   {
    _search = new Search();
   }

   return _search;
  }
  set
  {
   _search = value ?? new Search();
  }
 }

 public Column() { }
}


Search

/// <summary>
/// Search configuration for columns of a filter request
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public class Search : ISearch
{
 /// <summary>
 /// Search value to apply to this specific column.
 /// </summary>
 public virtual string Value { get; set; }

 /// <summary>
 /// Flag to indicate if the search term for this column should be 
 /// treated as regular expression (true) or not (false). 
 /// As with global search, normally server-side processing scripts 
 /// will not perform regular expression searching for performance 
 /// reasons on large data sets, but it is technically possible and at the discretion of your script.
 /// </summary>
 public virtual bool IsRegex { get; set; }

 public Search() { }

 public Search(string value, bool isRegex)
 {
  Value = value;
  IsRegex = isRegex;
 }
}


Sort

/// <summary>
/// Sort configuration for filter requests
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public class Sort : ISort
{
 /// <summary>
 /// Column to which ordering should be applied. This is an index reference 
 /// to the columns array of information that is also submitted to the server.
 /// </summary>
 public virtual int Column { get; set; }

 /// <summary>
 /// Ordering direction for this column. It will be ASC or DESC to 
 /// indicate ascending ordering or descending ordering, respectively.
 /// </summary>
 public virtual int Direction { get; set; }

 public Sort() { }

 public Sort(int column, int direction)
 {
  Column = column;
  Direction = direction;
 }

 public Sort(int column, bool asc)
 {
  Column = column;
  Direction = asc ? 1 : 0;
 }
}


ModelBinder

Để sử dụng ModelBinder, register trong Global.asax

public class BinderConfig
{
 public static void RegisterBinders(ModelBinderDictionary modelBinderDictionary)
 {
  modelBinderDictionary.Add(typeof(DataTablesRequest), new ModelBinder());
 }
}

...Global.asax
protected void Application_Start()
{
 ...
 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
 RouteConfig.RegisterRoutes(RouteTable.Routes);
 BundleConfig.RegisterBundles(BundleTable.Bundles);

 // Register DataTables FilterRequest
 BinderConfig.RegisterBinders(ModelBinders.Binders);
 ...
}


Model để parse request

/// <summary>
/// Model binder for datatables.js parameters
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public class ModelBinder : IModelBinder
{
 private readonly Type _concreteType;

 public ModelBinder(Type concreteType = null)
 {
  if (concreteType != null)
  {
   if (!concreteType.IsClass)
   {
    throw new NotSupportedException();
   }

   if (!concreteType.GetInterfaces().Any(x => x == typeof(IDataTablesRequest)))
   {
    throw new NotSupportedException();
   }

   _concreteType = concreteType;
  }
  else
  {
   _concreteType = typeof(DataTablesRequest);
  }
 }

 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
 {
  var request = Activator.CreateInstance(_concreteType) as IDataTablesRequest;

  Parse(ref request, controllerContext.HttpContext.Request.QueryString);
  Parse(ref request, controllerContext.HttpContext.Request.Form);

  return request;
 }

 private void Parse(ref IDataTablesRequest request, NameValueCollection collection)
 {
  var otherValues = new Dictionary<string, object>();

  foreach (string key in collection.AllKeys)
  {
   if (key == "draw")
   {
    request.Draw = GetValue<int>(collection[key]);
   }
   else if (key == "start")
   {
    request.Start = GetValue<int>(collection[key]);
   }
   else if (key == "length")
   {
    request.Length = GetValue<int>(collection[key]);
   }
   else if (key == "search[value]")
   {
    request.Search.Value = GetValue<string>(collection[key]);
   }
   else if (key == "search[regex]")
   {
    request.Search.IsRegex = GetValue<bool>(collection[key]);
   }
   else if (key.StartsWith("order"))
   {
    ParseSorting(ref request, key, collection[key]);
   }
   else if (key.StartsWith("columns"))
   {
    ParseColumn(ref request, key, collection[key]);
   }
   else
   {
    otherValues.Add(key, collection[key]);
   }
  }

  FormConvertHelper.ReadForm(request, otherValues,
   prop => FormConvertHelper.GetPropertiesFromType(typeof(IDataTablesRequest))
    .Select(x => x.Name).Contains(prop.Name));
 }

 private void ParseSorting(ref IDataTablesRequest request, string key, object value)
 {
  var match = Regex.Match(key, @"order\[([0-9]+)\](.+)");
  if (match.Success && match.Groups.Count == 3)
  {
   var index = Convert.ToInt32(match.Groups[1].Value);
   var propertyName = match.Groups[2].Value;

   while (index >= request.Sort.Count)
   {
    request.Sort.Add(new Sort());
   }

   if (propertyName == "[column]")
   {
    request.Sort[index].Column = GetValue<int>(value);
   }

   else if (propertyName == "[dir]")
   {
    request.Sort[index].Direction =
     string.Compare(GetValue<string>(value), "asc", true) == 0 ?
      1 : 0;
   }
  }
 }

 private void ParseColumn(ref IDataTablesRequest request, string key, object value)
 {
  var match = Regex.Match(key, @"columns\[([0-9]+)\](.+)");
  if (match.Success && match.Groups.Count == 3)
  {
   var index = Convert.ToInt32(match.Groups[1].Value);
   var propertyName = match.Groups[2].Value;

   IColumn currentColumn = null;

   if (!request.Columns.ContainsKey(index))
   {
    currentColumn = new Column();
    request.Columns.Add(index, currentColumn);
   }
   else
   {
    currentColumn = request.Columns[index];
   }

   if (propertyName == "[data]")
   {
    currentColumn.Data = GetValue<string>(value);
   }
   else if (propertyName == "[name]")
   {
    currentColumn.Name = GetValue<string>(value);
   }
   else if (propertyName == "[searchable]")
   {
    currentColumn.Searchable = GetValue<bool>(value);
   }
   else if (propertyName == "[orderable]")
   {
    currentColumn.Sortable = GetValue<bool>(value);
   }
   else if (propertyName == "[search][value]")
   {
    currentColumn.Search.Value = GetValue<string>(value);
   }
   else if (propertyName == "[search][regex]")
   {
    currentColumn.Search.IsRegex = GetValue<bool>(value);
   }
  }
 }

 private static T GetValue<T>(object value)
 {
  return (value == null) ? default(T) : (T)Convert.ChangeType(value, typeof(T));
 }
}


DataTablesRequest

/// <summary>
/// Datatables request
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public class DataTablesRequest : IDataTablesRequest
{
 /// <summary>
 /// Draw counter. This is used by DataTables to ensure that the AJAX returns 
 /// from server-side processing requests are drawn in sequence by DataTables 
 /// (Ajax requests are asynchronous and thus can return out of sequence). 
 /// </summary>
 public virtual int Draw { get; set; }

 /// <summary>
 /// Paging first record indicator. This is the start point in the 
 /// current data set (0 index based - i.e. 0 is the first record).
 /// </summary>
 public virtual int Start { get; set; }

 /// <summary>
 /// Number of records that the table can display in the current draw. 
 /// It is expected that the number of records returned will be equal 
 /// to this number, unless the server has fewer records to return. 
 /// Note that this can be -1 to indicate that all records should be returned 
 /// (although that negates any benefits of server-side processing!)
 /// </summary>
 public virtual int Length { get; set; }

 /// <summary>
 /// Global search component to be applied to all columns which have searchable as true.
 /// </summary>
 private ISearch _search;

 public virtual ISearch Search
 {
  get
  {
   if (_search == null)
   {
    _search = new Search();
   }

   return _search;
  }
  set
  {
   _search = value ?? new Search();
  }
 }

 /// <summary>
 /// The columns' sorting configuration.
 /// </summary>
 private List<ISort> _sort;

 public virtual List<ISort> Sort
 {
  get
  {
   if (_sort == null)
   {
    _sort = new List<ISort>();
   }

   return _sort;
  }
  set
  {
   _sort = value ?? new List<ISort>();
  }
 }

 /// <summary>
 /// The columns collection.
 /// </summary>
 private Dictionary<int, IColumn> _columns;

 public virtual Dictionary<int, IColumn> Columns
 {
  get
  {
   if (_columns == null)
   {
    _columns = new Dictionary<int, IColumn>();
   }

   return _columns;
  }
  set
  {
   _columns = value ?? new Dictionary<int, IColumn>();
  }
 }

 public DataTablesRequest()
 {
  Length = 10;
 }
}


DataTables response

Sau khi có response dạng IDataTablesResponse<Projectchuyển về dạng IDataTablesResponse<object>

DataTablesResponse

/// <summary>
/// Mutable page response
/// 
/// https://github.com/ALMMa/datatables.aspnet
/// https://github.com/offspringer/mvc.datatables/blob/master/Mvc.Datatables
/// </summary>
public abstract class DataTablesResponse
{
 /// <summary>
 /// The draw counter that this object is a response to - 
 /// from the draw parameter sent as part of the data request.
 /// </summary>
 [JsonProperty("draw")]
 public virtual int Draw { get; set; }

 /// <summary>
 /// Total records, before filtering (i.e. the total number of records in the database).
 /// </summary>
 [JsonProperty("recordsTotal")]
 public virtual int TotalRecords { get; set; }

 /// <summary>
 /// Total records, after filtering (i.e. the total number of records after 
 /// filtering has been applied - not just the number of records being returned for this page of data).
 /// </summary>
 [JsonProperty("recordsFiltered")]
 public virtual int TotalFilteredRecords { get; set; }

 /// <summary>
 /// The data to be displayed in the table. This is an array of data source objects, 
 /// one for each row, which will be used by DataTables. Note that this 
 /// parameter's name can be changed using the ajaxDT option's dataSrc property.
 /// </summary>
 [JsonProperty("data")]
 public virtual object[] Data { get; set; }

 /// <summary>
 /// Optional: If an error occurs during the running of the server-side processing script, 
 /// you can inform the user of this error by passing back the error 
 /// message to be displayed using this parameter. Do not include if there is no error.
 /// </summary>
 [JsonProperty("error")]
 public virtual string Error { get; set; }

 public DataTablesResponse() { }
}

/// <summary>
/// Type-safe page response
/// </summary>
/// <typeparam name="TSource">The type of the data array</typeparam>
public class DataTablesResponse<TSource> : DataTablesResponse, IDataTablesResponse<TSource>
{
 [JsonIgnore]
 public virtual TSource[] DataSource
 {
  get
  {
   return base.Data != null ? base.Data.Cast<TSource>().ToArray() : null;
  }
  set
  {
   base.Data = value != null ? value.Cast<object>().ToArray() : null;
  }
 }

 public DataTablesResponse() { }

 public DataTablesResponse(int draw, TSource[] dataSource)
 {
  Draw = draw;
  TotalRecords = dataSource.Length;
  TotalFilteredRecords = dataSource.Length;
  DataSource = dataSource;
 }

 public DataTablesResponse(int draw, int totalRecords, int totalDisplayRecords, TSource[] dataSource)
 {
  Draw = draw;
  TotalRecords = totalRecords;
  TotalFilteredRecords = totalDisplayRecords;
  DataSource = dataSource;
 }

 public virtual IDataTablesResponse<TSource> Transform(Func<TSource, TSource> rowTransform)
 {
  var data = DataSource.Select(rowTransform).ToArray();

  var response = new DataTablesResponse<TSource>(
   Draw, TotalRecords, TotalFilteredRecords, data);

  return response;
 }

 public virtual IDataTablesResponse<object> Transform<TMutableSource, TTransform>(
  Func<TMutableSource, TTransform> rowTransform)
 {
  var data = Data.Cast<TMutableSource>()
   .Select(rowTransform).Cast<object>().ToArray();

  var response = new DataTablesResponse<object>(
   Draw, TotalRecords, TotalFilteredRecords, data);

  return response;
 }
}