anchor.diff:  open Accord.Statistics.Models.Regression.Linear
   open Iveonik.Stemmers
   
  -[<Literal>]
  -let trainDataPath = "../data/train.csv"
  -type Train = CsvProvider<trainDataPath>
  +type Train = CsvProvider<"../data/train.csv">
   let train = Train.GetSample()
   
  -[<Literal>]
  -let testDataPath = "../data/test.csv"
  -type Test = CsvProvider<testDataPath>
  +type Test = CsvProvider<"../data/test.csv">
   let test = Test.GetSample()
   
  -[<Literal>]
  -let productDescriptionsPath = "../data/product_descriptions.csv"
  -type Products = CsvProvider<productDescriptionsPath>
  +type Products = CsvProvider<"../data/product_descriptions.csv">
   let products = Products.GetSample()
  +
  +type Attributes = CsvProvider<"../data/attributes.csv">
  +let attributes = Attributes.GetSample()
   
   let productDescMap =
       products.Rows
  
positive.code:     |> Map.ofSeq
  let inline productDescription uid = productDescMap |> Map.find uid
  
  let stem word =
      let stemmer = EnglishStemmer() // NOTE stemmer not thread-safe
      let stemmed = stemmer.Stem word
      if stemmed.Length < word.Length then stemmed // HACK workaround stemmer output like "vanity" -> "vaniti"
      else word
  
  let stemmedWordMatch (words:string) (title:string) (desc:string) =
      let isMatch input word =
          let word' = stem word |> Regex.Escape // TODO strip punctuation?
          Regex.IsMatch(input, sprintf @"\b%s" word', RegexOptions.IgnoreCase)
      let uniqueWords = words.Split([|' '|], StringSplitOptions.RemoveEmptyEntries) |> Array.distinct
      let numberInTitle = uniqueWords |> Array.filter (isMatch title) |> Array.length
      let numberInDescription = uniqueWords |> Array.filter (isMatch desc) |> Array.length
      numberInTitle, numberInDescription, uniqueWords.Length 
  
  let stringContainsMatch (words:string) (title:string) (desc:string) =
      let words' = words.Split(' ')
      let uniqueWords = words' |> Array.distinct
      let numberInTitle = uniqueWords |> Array.filter (fun w -> title.ToLower().Contains(w.ToLower())) |> Array.length
      let numberInDescription = uniqueWords |> Array.filter (fun w -> desc.ToLower().Contains(w.ToLower())) |> Array.length
      numberInTitle, numberInDescription, uniqueWords.Length 
  
  let inline toFloatArray (a,b,c) = [| float a; float b; float c |]
  
  let trainInput = 
      train.Rows
      |> Seq.map (fun w -> stemmedWordMatch w.Search_term w.Product_title (productDescription w.Product_uid) |> toFloatArray)
      |> Seq.toArray
  
  let trainOutput = 
  
negative.code: 
  let testInput = 
      test.Rows 
      |> Seq.map (fun w ->
          stemmedWordMatch
            w.Search_term
            w.Product_title
            (productDescription w.Product_uid)
            (attribs w.Product_uid)
            (brandName w.Product_uid))
      |> Seq.toArray
  
  let testOutput = target.Compute(testInput)
  let inline bracket n = Math.Max(1., Math.Min(3., n))
  let testOutput' = testOutput |> Seq.map bracket
  
  let submission = 
      Seq.zip test.Rows testOutput'
  