导读:ML.NET系列文章

本文将基于ML.NET v0.2预览版,重点介绍提取特征的思路和方法,实现德州扑克牌型分类器。

先介绍一下德州扑克的基本牌型,一手完整的牌共有五张扑克,10种牌型分别是:

1. 高牌,花色和点数同时没有相同的牌。

2. 一对,点数有且仅有两张相同的牌。

3. 两对,两张相同点数的牌,加另外两张相同点数的牌。

4. 三条,有三张同一点数的牌。

5. 顺子,五张顺连的牌。

6. 同花,五张同一花色的牌。

7. 葫芦,三张同一点数的牌,加一对其他点数的牌。

8. 四条,有四张同一点数的牌。

9. 同花顺,同一花色五张顺连的牌。

10. 皇家同花顺,最高点数是A的同花顺的牌。

这一次我们将使用逻辑回归模型,来训练数据完成我们想要的分类模型。

准备数据集


数据来源在Poker Hand Data Set,下载链接为:poker-hand-testing.datapoker-hand-training-true.data。内容类似如下:

3,92,3,3,2,2,9,3,5,1
4,4,1,11,2,9,4,13,2,7,0
1,5,1,9,2,8,2,4,4,3,0
4,12,4,7,4,5,2,10,2,2,0
4,3,2,4,4,13,3,6,4,12,0
2,5,4,5,4,1,4,9,2,7,1
2,12,3,12,3,7,3,11,2,7,2
4,13,2,6,4,6,4,10,4,9,1
...

说明一下每一行的格式:

第1张花色,第1张点数,第2张花色,第2张点数,第3张花色,第3张点数,第4张花色,第4张点数,第5张花色,第5张点数,牌型

花色是1-4代表红心,黑桃,方块,梅花。点数1表示A,2-10保持不变,11表示J,12表示Q,13表示K。

特征分析


前几篇数据集的内容,基本上分割好就是特征了,这一次不同,每一行的数值仅仅是元数据,也就是说,通过五张牌的花色和点数值是不能直接和牌型形成一一对应的联系,需要先按本文开头介绍的10种牌型的描述,找到关键可数值化的字段。因此,我选择了这样一些字段:对子数,是否是三条,是否是四条,是否是顺子,是否同花。通过这5个字段值的组合,一定能判断出牌型。

于是,我定义出我想要的数据类型:

public class PokerHandData
{
[Column(ordinal: "")]
public float S1;
[Column(ordinal: "")]
public float C1;
[Column(ordinal: "")]
public float S2;
[Column(ordinal: "")]
public float C2;
[Column(ordinal: "")]
public float S3;
[Column(ordinal: "")]
public float C3;
[Column(ordinal: "")]
public float S4;
[Column(ordinal: "")]
public float C4;
[Column(ordinal: "")]
public float S5;
[Column(ordinal: "")]
public float C5;
[Column(ordinal: "", name: "Label")]
public float Power;
[Column(ordinal: "")]
public float IsSameSuit; [Column(ordinal: "")]
public float IsStraight; [Column(ordinal: "")]
public float FourOfKind; [Column(ordinal: "")]
public float ThreeOfKind; [Column(ordinal: "")]
public float PairsCount;
}

S表示花色,C表示点数,Power表示牌型,PairsCount表示对子数,ThreeOfKind表示是否是三条,FourOfKind表示是否是四条,IsStraight表示是否顺子,IsSameSuit表示是否同花。

判断是否同花,只需要比较S1-S5的值即可。

public float GetIsSameSuit()
{
if (S1 == S2 && S2 == S3 && S3 == S4 && S4 == S5)
return ;
else
return ;
}

判断其它几个特征,我需要一个通用方法,先统计出每一行的C1-C5,每种点数出现的次数。

static Dictionary<int, int> GetValueCountsOfCondition(IEnumerable<int> cards)
{
var dic = new Dictionary<int, int>(); foreach (var item in cards)
{
if (dic.ContainsKey(item))
{
dic[item] += ;
}
else
{
dic.Add(item, );
}
}
return dic;
}

然后再按特征涵义计算值。

public float GetFourOfKind()
{
return GetCountOfCondition();
} public float GetThreeOfKind()
{
return GetCountOfCondition();
} public float GetPairsCount()
{
return GetCountOfCondition();
} private IEnumerable<int> GetCards()
{
if (cards == null)
{
cards = new[] { Convert.ToInt32(C1), Convert.ToInt32(C2), Convert.ToInt32(C3), Convert.ToInt32(C4), Convert.ToInt32(C5) };
} return cards;
} private float GetCountOfCondition(int target)
{
if (valueCounts == null)
{
valueCounts = GetValueCountsOfCondition(GetCards());
} return valueCounts.Count(i => i.Value == target);
}

判断是否为顺子的方法,简单而直接,就是看间隔差是不是为1,或者最高点有A剩下的必须是10、J、Q、K,都算顺子。

public float GetIsStraight()
{
var keys = GetCards().ToArray();
Array.Sort(keys);
if (keys[] - keys[] == keys[] - keys[] && keys[] - keys[] == keys[] - keys[] && keys[] - keys[] == keys[] - keys[] && keys[] - keys[] == )
{
return ;
}
else if (keys[] == && keys[] == && keys[] == && keys[] == && keys[] == )
{
return ;
}
else
{
return ;
}
}

加载数据


这次由于使用了ML.NET v0.2,该版本的LearningPipeline新增了一种支持集合类型的数据源。因此,我将示范一种全新的载入数据集的方法,先以文件载入元数据,然后直接初始化特征的值。

static IEnumerable<PokerHandData> LoadData(string path)
{
using (var environment = new TlcEnvironment())
{
var pokerHandData = new List<PokerHandData>();
var textLoader = new Microsoft.ML.Data.TextLoader(path).CreateFrom<PokerHandData>(useHeader: false, separator: ',', trimWhitespace: false);
var experiment = environment.CreateExperiment();
var output = textLoader.ApplyStep(null, experiment) as ILearningPipelineDataStep; experiment.Compile();
textLoader.SetInput(environment, experiment);
experiment.Run(); var data = experiment.GetOutput(output.Data); using (var cursor = data.GetRowCursor((a => true)))
{
var getters = new ValueGetter<float>[]{
cursor.GetGetter<float>(),
cursor.GetGetter<float>(),
cursor.GetGetter<float>(),
cursor.GetGetter<float>(),
cursor.GetGetter<float>(),
cursor.GetGetter<float>(),
cursor.GetGetter<float>(),
cursor.GetGetter<float>(),
cursor.GetGetter<float>(),
cursor.GetGetter<float>(),
cursor.GetGetter<float>()
}; while (cursor.MoveNext())
{
float value0 = ;
float value1 = ;
float value2 = ;
float value3 = ;
float value4 = ;
float value5 = ;
float value6 = ;
float value7 = ;
float value8 = ;
float value9 = ;
float value10 = ;
getters[](ref value0);
getters[](ref value1);
getters[](ref value2);
getters[](ref value3);
getters[](ref value4);
getters[](ref value5);
getters[](ref value6);
getters[](ref value7);
getters[](ref value8);
getters[](ref value9);
getters[](ref value10); var hands = new PokerHandData()
{
S1 = value0,
C1 = value1,
S2 = value2,
C2 = value3,
S3 = value4,
C3 = value5,
S4 = value6,
C4 = value7,
S5 = value8,
C5 = value9,
Power = value10
};
hands.Init();
pokerHandData.Add(hands);
}
} return pokerHandData;
}
}

其中PokerHandData类增加一个初始化的方法。

public void Init()
{
IsSameSuit = GetIsSameSuit();
IsStraight = GetIsStraight();
FourOfKind = GetFourOfKind();
ThreeOfKind = GetThreeOfKind();
PairsCount = GetPairsCount();
}

训练模型


预测的结构定义,以计分为目标,float[]类型表示是对每一种牌型有一个得分,分值越高属于那一种牌型的概率越大。

public class PokerHandPrediction
{
[ColumnName("Score")]
public float[] PredictedPower;
}

模型的选择是LogisticRegressionClassifier,CollectionDataSource就是用来创建集合类型数据载入的对象。而特征的指定不再是全部字段,而是之前增加的那几个。

public static PredictionModel<PokerHandData, PokerHandPrediction> Train(IEnumerable<PokerHandData> data)
{
var pipeline = new LearningPipeline();
var collection = CollectionDataSource.Create(data);
pipeline.Add(collection);
pipeline.Add(new ColumnConcatenator("Features", "IsSameSuit", "IsStraight", "FourOfKind", "ThreeOfKind", "PairsCount"));
pipeline.Add(new LogisticRegressionClassifier());
var model = pipeline.Train<PokerHandData, PokerHandPrediction>();
return model;
}

预测结果


首先,对预测的得分,我们需要判断一个概率倾向。

static string GetPower(float[] nums)
{
var index = -;
var last = 0F;
for (int i = ; i < nums.Length; i++)
{
if (nums[i] > last)
{
index = i;
last = nums[i];
}
}
var suit = string.Empty;
switch (index)
{
case :
suit = "高牌";
break;
case :
suit = "一对";
break;
case :
suit = "两对";
break;
case :
suit = "三条";
break;
case :
suit = "顺子";
break;
case :
suit = "同花";
break;
case :
suit = "葫芦";
break;
case :
suit = "四条";
break;
case :
suit = "同花顺";
break;
case :
suit = "皇家同花顺";
break; }
return suit;
}

最后就是进行预测的部分了。

public static void Predict(PredictionModel<PokerHandData, PokerHandPrediction> model)
{
var test1 = new PokerHandData
{
S1 = ,
C1 = ,
S2 = ,
C2 = ,
S3 = ,
C3 = ,
S4 = ,
C4 = ,
S5 = ,
C5 =
}; var test2 = new PokerHandData
{
S1 = ,
C1 = ,
S2 = ,
C2 = ,
S3 = ,
C3 = ,
S4 = ,
C4 = ,
S5 = ,
C5 =
};
test1.Init();
test2.Init();
IEnumerable<PokerHandData> pokerHands = new[]
{
test1,
test2
};
IEnumerable<PokerHandPrediction> predictions = model.Predict(pokerHands);
Console.WriteLine();
Console.WriteLine("PokerHand Predictions");
Console.WriteLine("---------------------"); var pokerHandsAndPredictions = pokerHands.Zip(predictions, (pokerHand, prediction) => (pokerHand, prediction));
foreach (var (pokerHand, prediction) in pokerHandsAndPredictions)
{
Console.WriteLine($"PokerHand: {ShowHand(pokerHand)} | Prediction: { GetPower(prediction.PredictedPower)}");
}
Console.WriteLine(); }

创建项目的步骤请参看本文开头导读给出的文章链接,不再赘述,运行结果如下:

最后放出源代码文件:下载

希望读者们保持对ML.NET的持续关注,相信新的特性一定能实现更复杂有趣的场景。

使用ML.NET实现德州扑克牌型分类器的更多相关文章

  1. 洛谷 P3078 [USACO13MAR]扑克牌型Poker Hands

    P3078 [USACO13MAR]扑克牌型Poker Hands 题目描述 Bessie and her friends are playing a unique version of poker ...

  2. 洛谷P3078 [USACO13MAR]扑克牌型Poker Hands

    题目描述 Bessie and her friends are playing a unique version of poker involving a deck with \(N\) (\(1 \ ...

  3. P3078 [USACO13MAR]扑克牌型Poker Hands

    题目描述 Bessie and her friends are playing a unique version of poker involving a deck with N (1 <= N ...

  4. 【贪心 思维题】[USACO13MAR]扑克牌型Poker Hands

    看似区间数据结构的一道题 题目描述 Bessie and her friends are playing a unique version of poker involving a deck with ...

  5. 「Luogu P3078 [USACO13MAR]扑克牌型Poker Hands」

    本题有\(O(N)\)的优秀做法,但是因为在考场上不一定能想到,就来分享一种\(O(N\log_2N)\)的做法.虽然有点慢,但是可以过. 前置芝士 线段树:提高组及以上必备内容,不会的同学可以学习一 ...

  6. ML.NET

    ML.NET http://www.cnblogs.com/BeanHsiang/category/1218714.html 随笔分类 - 使用ML.NET实现NBA得分预测 摘要: 本文将介绍一种特 ...

  7. 基于AForge.Net框架的扑克牌识别

    原文:基于AForge.Net框架的扑克牌识别 © 版权所有 野比 2012 原文地址:点击查看 作者:Nazmi Altun Nazmi Altun著,野比 译  下载源代码 - 148.61 KB ...

  8. 机器学习ML策略

    1.为什么是ML策略 例如:识别cat分类器的识别率是90%,怎么进一步提高识别率呢? 想法: (1)收集更多数据 (2)收集更多的多样性训练样本 (3)使用梯度下降训练更长时间 (4)尝试Adam代 ...

  9. (Step1-500题)UVaOJ+算法竞赛入门经典+挑战编程+USACO

    http://www.cnblogs.com/sxiszero/p/3618737.html 下面给出的题目共计560道,去掉重复的也有近500题,作为ACMer Training Step1,用1年 ...

随机推荐

  1. Quartz+JAVA+Servlet实现任务调度系统(简洁)

    前言 该系统使用场景: 在12306上买了一张火车票,30分钟内需要支付(需要添加一个倒计时),30分钟还没有支付就请求取消订单的接口(自动根据url请求),如果支付了收到了支付的回调通知后,就删除计 ...

  2. python 判断连个 Path 是否是相同的文件夹

    python 判断连个 Path 是否是相同的文件夹 import os os.path.normcase(p1) == os.path.normcase(p2) normcase() 在 windo ...

  3. pdfjs预览pdf文件的两种方式(可复制)

    1.以图片形式进行展示: version:采用1.x版本,2.0版本会有字体显示不完整的问题:参考 这里使用1.8.170 <script th:src="@{/pdfjs/build ...

  4. Win10 教育版

    Windows 10 版本 1607 引入了专为 K-12 机构的特有需求而设计的两个版本:Windows 10 专业教育版和 Windows 10 教育版. 这些版本为不断发展的 K-12 教育 I ...

  5. 微信小程序视频学习笔记

    [清华大学]学做小程序 https://www.bilibili.com/video/av21987398 2.2创建项目和文件结构 小程序包含一个描述整体程序的app和多个描述各自页面的page 配 ...

  6. Day2----《Pattern Recognition and Machine Learning》Christopher M. Bishop

    用一个例子来讲述regression. 采用sin(2*pi*x)加入微弱的正态分布噪声的方式来获得一些数据,然后用多项式模型来进行拟合. 在评价模型的准确性时,采用了误差函数的方式,用根均方误差的方 ...

  7. ESP32 Eclipse开发环境构建与问题总结

    搞了一个多星期的eclipse环境构建,终于成功了,在此记录下期间遇到的问题. 以下为遇到的几点问题的解决方法: 1.使用的版本为V3.1版本,版本时间为2018年09月07日,可以直接在以下路径下载 ...

  8. 再回首数据结构—数组(Golang实现)

    数组为线性数据结构,通常编程语言都有自带了数组数据类型结构,数组存放的是有个相同数据类型的数据集: 为什么称数组为线性数据结构:因为数组在内存中是连续存储的数据结构,数组中每个元素最多只有左右两个方向 ...

  9. IOS开发中关于runtime的认识

    首先要知道我们写的代码在程序运行过程中都会被转化成runtime的C代码执行. runtime突出的一点就是OC中消息传递机制的应用.objc_msgsend(target,SEL); 首先我们先看一 ...

  10. ip锁死怎么设置ip地址

    单击电脑网络连接图标,打开网络和共享中心   点击本地连接   点击详细信息,即可看到IP地址.子网掩码.默认网关.DNS服务器信息   再点击本地连接状态下的属性   找到Internet 协议版本 ...