摘要:昨天给大家演示简单的文本聚类,但要给每个聚类再提取一两个关键词用于表示该聚类。我们还是用TFIDF算法来做,因为这是比较简单的提取特征算法,不过这里的TF是指某词在本聚类内所有文章的词频,而不是本文章内出现的次数,IDF还是在所有文章里出现的倒文档频率。
原理:1、先给本聚类内的所有文档进行分词,然后用一个字典保存每个词出现的次数
2、遍历每个词,得到每个词在所有文档里的IDF值,和在本聚类内出现的次数(TF)相乘的值
3、用一个字典(key是词,value是TF*IDF权重)来保存所有的词信息,然后按value对字典排序,最后取权重排名靠前的几个词作为关键词
测试输入如下
================================
a 奥运 拳击 入场券 基本 分罄 邹市明 夺冠 对手 浮出 水面
a 股民 要 清楚 自己 的 目的
a 印花税 之 股民 四季
a ASP.NET 自定义 控件 复杂 属性 声明 持久性 浅析
a 运动员 行李 将 “后 上 先 下” 奥运 相关 人员 行李 实名制
a asp.net 控件 开发 显示 控件 内容
a 奥运 票务 网上 成功 订票 后 应 及时 到 银行 代售 网点 付款
a 某 心理 健康 站 开张 后 首 个 咨询 者 是 位 新 股民
a 残疾 女 青年 入围 奥运 游泳 比赛 创 奥运 历史 两 项 第一
a 介绍 一 个 ASP.net MVC 系列 教程
a 在 asp.net 中 实现 观察者 模式 ,或 有 更 好 的 方法 (续)
a 输 大钱 的 股民 给 我们 启迪
a Asp.Net 页面 执行 流程 分析
a 杭州 股民 放 鞭炮 庆祝 印花税 下调
================================
数据还是昨天的数据,但每个文章前面都加了个a,所以这个词的IDF肯定很低,如果单村用词频来提取关键词,这个a肯定被当场关键词了,所以要乘以IDF值来调整特征提取的精度。我们要用程序把上面的文档分成3类,并提取每个类的两个关键词
我给TFIDFMeasure类加了一个GetKeyword的方法,第一个参数是传入几个文档id列表,第二个参数是要在这几个文档里提取几个关键词,下面是使用该方法的代码
WawaKMeans kmeans = new WawaKMeans(data, K);
//5、开始迭代
kmeans.Start();
//6、获取聚类结果并输出
WawaCluster[] clusters = kmeans.Clusters;
StringBuilder sb = new StringBuilder();
foreach (WawaCluster cluster in clusters)
{
List<int> members = cluster.CurrentMembership;
//获取该聚类的关键词并打印
IEnumerable<string> keywords = tf.GetKeyword(cluster.CurrentMembership, 2);
StringBuilder sbTemp = new StringBuilder();
sbTemp.Append("---------");
foreach (string s in keywords)
{
sbTemp.AppendFormat("{0},", s);
}
sbTemp.Append("-------\r\n");
Console.WriteLine(sbTemp);
//打印该聚类的成员
sb.Append(sbTemp.ToString());
foreach (int i in members)
{
Console.WriteLine(docs[i]);
sb.AppendFormat("{0}\r\n", docs[i]);
}
}
再看GetKeyword方法的实现
/// 获取某组文档的关键词
/// </summary>
/// <param name="arr"></param>
/// <param name="count"></param>
/// <returns></returns>
public IEnumerable<string> GetKeyword(List<int> arr, int count)
{
//1、给每个文档分词并保存在一个列表里
List<string> allWords = new List<string>();
foreach (int i in arr)
{
//这里把一个文档里出现的多个词进行消重
allWords.AddRange(GetDistinctWords(_tokenizer.Partition(_docs[i])));
}
//2、用一个字典保存词的词频,key是词,value是重复次数
Dictionary<string , int> tfDict = SortByDuplicateCount(allWords);
//3、遍历已排序的词频字典,并获取每个词的IDF值,并把更新后的结果放入一个tfidfDict词典
//该词典的key是词,value是tfidf值
Dictionary<string,float> tfidfDict = new Dictionary<string, float>(tfDict.Count);
foreach (KeyValuePair<string, int> pair in tfDict)
{
int tremIndex;
if(_tremIndex.TryGetValue(pair.Key,out tremIndex))
{
float idf = GetInverseDocumentFrequency(tremIndex);
tfidfDict.Add(pair.Key, pair.Value * idf);
}
}
//4、给tfidf字典俺权重排序
tfidfDict = GetSortByValueDict(tfidfDict);
//5、更新要提取的关键词数量
int keywordCount = count;
if (keywordCount > tfidfDict.Count)
keywordCount = tfidfDict.Count;
//6、用一个数组保存tfidf字典的keys,这些key已排序
string[] keywordArr = new string[tfidfDict.Count];
tfidfDict.Keys.CopyTo(keywordArr,0);
//7、在关键词数组里取出前几个关键词返回给调用者
List<string> result = new List<string>(keywordCount);
int tempCount = 0;
foreach (string str in keywordArr)
{
tempCount++;
result.Add(str);
if(tempCount >=keywordCount) break;
}
return result;
}
这里面用到一个SortByDuplicateCount方法,是对一个集合里的元素按重复次数排序,输出一个字典,字典的key是原始元素,value是出现次数,并按出现次数从大到小排序,像 { "abcd", "ab", "b", "a", "abcd", "ab", "ab", "ab", "cd", "cd", "cd" }这样一个集合应该输入如下结果。
ab-4
cd-3
abcd-2
b-1
a-1
原理是先用一个字典计算每个元素的出现次数,然后把该字典按value的大小排序,下面是实现代码
/// 把一个集合按重复次数排序
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="inputList"></param>
/// <returns></returns>
public static Dictionary<T, int> SortByDuplicateCount<T>(IList<T> inputList)
{
//用于计算每个元素出现的次数,key是元素,value是出现次数
Dictionary<T, int> distinctDict = new Dictionary<T, int>();
for (int i = 0; i < inputList.Count; i++)
{
//这里没用trygetvalue,会计算两次hash
if (distinctDict.ContainsKey(inputList[i]))
distinctDict[inputList[i]]++;
else
distinctDict.Add(inputList[i], 1);
}
Dictionary<T, int> sortByValueDict = GetSortByValueDict(distinctDict);
return sortByValueDict;
}
这里用到一个把一个字典按值的大小排序的方法GetSortByValueDict,代码如下,是泛型的
/// 把一个字典俺value的顺序排序
/// </summary>
/// <typeparam name="K"></typeparam>
/// <typeparam name="V"></typeparam>
/// <param name="distinctDict"></param>
/// <returns></returns>
public static Dictionary<K, V> GetSortByValueDict<K,V>(IDictionary<K, V> distinctDict)
{
//用于给tempDict.Values排序的临时数组
V[] tempSortList = new V[distinctDict.Count];
distinctDict.Values.CopyTo(tempSortList, 0);
Array.Sort(tempSortList); //给数据排序
Array.Reverse(tempSortList);//反转
//用于保存按value排序的字典
Dictionary<K, V> sortByValueDict =
new Dictionary<K, V>(distinctDict.Count);
for (int i = 0; i < tempSortList.Length; i++)
{
foreach (KeyValuePair<K, V> pair in distinctDict)
{
//比较两个泛型是否相当要用Equals,不能用==操作符
if (pair.Value.Equals(tempSortList[i]) && !sortByValueDict.ContainsKey(pair.Key))
sortByValueDict.Add(pair.Key, pair.Value);
}
}
return sortByValueDict;
}
对一个文章内出现的多个词进行消重是因为如果一个文章里堆叠关键词会影响本聚类关键词提取的准确性,所以要排重,算法如下,也是泛型的
/// 对一个数组进行排重
/// </summary>
/// <param name="scanKeys"></param>
/// <returns></returns>
public static IEnumerable<T> GetDistinctWords<T>(IEnumerable<T> scanKeys)
{
T temp = default(T);
if (scanKeys.Equals(temp))
return new T[0];
else
{
Dictionary<T, T> fixKeys = new Dictionary<T, T>();
foreach (T key in scanKeys)
{
fixKeys[key] = key;
}
T[] result = new T[fixKeys.Count];
fixKeys.Values.CopyTo(result, 0);
return result;
}
}
最后效果如下
Iteration 0...
Iteration 1...
---------asp,net,-------
a ASP.NET 自定义 控件 复杂 属性 声明 持久性 浅析
a asp.net 控件 开发 显示 控件 内容
a 介绍 一 个 ASP.net MVC 系列 教程
a 在 asp.net 中 实现 观察者 模式 ,或 有 更 好 的 方法 (续)
a Asp.Net 页面 执行 流程 分析
---------股民,印花税,-------
a 股民 要 清楚 自己 的 目的
a 印花税 之 股民 四季
a 某 心理 健康 站 开张 后 首 个 咨询 者 是 位 新 股民
a 输 大钱 的 股民 给 我们 启迪
a 杭州 股民 放 鞭炮 庆祝 印花税 下调
---------奥运,拳击,-------
a 奥运 拳击 入场券 基本 分罄 邹市明 夺冠 对手 浮出 水面
a 运动员 行李 将 “后 上 先 下” 奥运 相关 人员 行李 实名制
a 奥运 票务 网上 成功 订票 后 应 及时 到 银行 代售 网点 付款
a 残疾 女 青年 入围 奥运 游泳 比赛 创 奥运 历史 两 项 第一
可以看到,提取的关键字还是准确的,并没把a当成关键字。
很奇怪的现象:用firefox上网,某些网站打开总是会提示 无法在XXX找到该服务器。但是使用其他浏览器,比如360却可以正常打开。
我已经将firefox加入了防火墙的信任列表,但是仍旧是这样。
而在同时,却惊人的发现其他浏览器却可以上。为什么?
另外发现的一个现象是:我在一个群里讨论这个事情,结果被人批了,因为我说过360可以打开但是firefox怎么都打不开。
然后他告诉我说firefox肯定可以打开,一定是你弄反了。不可思议的逻辑。firefox确实是很多网站都打不开,百度一下,很多结果。
发现很奇特的规律:当拿firefox跟360浏览器来比较的时候,总是有人会跳出来说firefox一定比360好?当拿百度跟谷歌相比的时候,总是有人会来说百度一定比谷歌差。要知道谷歌现在连打都打不开,不要来谈什么原因,打都打不开了,对于你的上网还有意义了吗?等待四五分钟很高端?实事求是
还有一个比较让人匪夷所思的现象 :某网站的下载应用,iso版本的下载量不到安卓版的一半,但是但是他们的应用下载连接中,iso版本的下载地址永远放在最显眼的位置,而安卓版的下载连接永远都放在最角落的位置。而且宣传的图片上都是用的iphone,其实看到这样,心里也明白。用iphone有面子。
在不久的将来我相信Web App会流行的非常广, 能看到未来才能主宰未来。对于我们开发人员来说我觉得想成就一件伟大的事情,需要过硬的技术和好的想法,再加上决不放弃的精神,一定可以成功的。
以下在Mac下测试成功
安装Web Server
我用的是Apache Tomcat 6.0, 测试是否可以正常试用. 打开你的Terminal.
启动你的Web Server命令如下:
sudo sh startup.sh
当你看到如下图的时候,说明已经启动成功了.
关闭服务的命令是:
sudo sh shutdown.sh
如果遇到文件权限问题,而无法执行命令时。
你需要执行一条:
sudo chmod 755bin/*.sh -à 把文件设置成可读可写
接下来在你的浏览器中输入localhost:8080
到这里我们的WebServer就算安装成功了.
安装我们的SenchaCmd
它是一个可装的工具,直接双击就可以安装。安装完成后在你的用户目录下会出现一个bin目录.
然后再一次的打开我们的Terminal.
设置我们的环境变量. 如图
当你能看到上图的时候, 你的SenchaCmd已经成功安装了.这里有一个问题,就是当你关闭Terminal,再打开的时间.要重新设置PATH. 因为我们设置的变量没有存到系统中。
我知道可以存到/etc/profile文件中, 该文件必须有root权限才可以添加. 建议使用vi编辑. 我没有把PATH写到/etc/profile文件中, 而是写到了~/bash_profile文件中。
所以我在每次使用Sencha命令时:需要在Terminal中执行一条source~/bash_profile来加载环境变量。
安装SenchaTouch 2 SDK
把我们的SenchaTouch SDK解压后,拷贝到webapps下。如图
然后在浏览器中输入:
localhost:8080/touch-2.2.1/SETUP.html
基本工作已经完成,现在可以创建我们的第一个WebApp.
打开我们的Terminal,cd 到webapps/touch-2.2.1目录下。
执行命令
sencha generate apphello_world ../hello_world如图所示
如果你能看到上面的信息说明你的第一个Web App已经创建成功了。
下面是我们刚才创建的目录:
接下来运行我们的WebApp. 这里需要重启一下我们的ApachaTomcat:
如果你能看到下面的图,说明你已经成功了.
继续完成我们的HelloWorld
在此建议大家把我在Chatter中发的WindowResizer装到你的浏览器中,方便调试窗口的大小。
推荐一个开发js很不错的工具WebStorm
用这个工具打开刚才创建的WebApp
在hell_world目录下创建一个文件叫hello_world.html
我们的第一个Helloworld终于出来了。
我们的第二个比较好看的hello Sencha页面.
代码如下:
到这儿基本上已经完成了Web App的开发。
有两个东西需要提醒一下:
1) 在启动Apache Tomcat出现权限问题时。需要执行一条: sudo chmod 755 *.sh, 对所有的sh加根.
2) 建议大家的用Chrome的浏览器, 然后在Chrome上装一个Window Resizer 的插件,可以调整Chrome的大小.
有什么问题可以问我, 邮箱是: samba.gao@hotmail.com