建立文本數據數學描寫敘述的過程分為三個步驟:文本預處理、建立向量空間模型和優化文本向量。
文本預處理主要採用分詞、停用詞過濾等技術將原始的文本字符串轉化為詞條串或者特點的符號串。文本預處理之后,每個文本的詞條串被進一步轉換為一個文本向量,向量的每一維相應一個詞條,其值反映的是這個詞條與這個文本之間的類似度。類似度有非常多不同的計算方法。所以優化文本向量就是採用最為合適的計算方法來規范化文本向量,使其能更好地應用於文本分類和文本聚類等方面。
TFIDF算法
TF-IDF使得一個單詞能盡量與文本在語義上相關。TF-IDF算法的實現步驟:
經過試驗發現,用TFIDF/max(TFIDF)的方法效果是最好的。詳細代碼例如以下:
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/** * 經過試驗發現,用TFIDF/max(TFIDF)的方法效果是最好的 * @author Angela */
public class TFIDF {
private Map<String,Integer> TF;//文本詞頻集
private Map<String,Double> IDF;//特征-逆文檔頻率集
/** * 構造方法,初始化TF和IDF */
public TFIDF(Map<String,Integer> TF,Map<String,Double> IDF){
this.TF=TF;
this.IDF=IDF;
}
/** * 計算文本特征集的tf-idf權值 * @return filePath文件的特征-TFIDF集 */
public Map<String,Double> getTFIDF(){
Map<String,Double> tfidf=new HashMap<String,Double>();
for(Map.Entry<String,Integer> me: TF.entrySet()){
String f=me.getKey();
double weight=me.getValue()*IDF.get(f);
tfidf.put(f, weight);
}
return tfidf;
}
/** * 計算文本特征集的對數tf-idf權值 * @return filePath文件的特征-TFIDF集 */
public Map<String,Double> getLogTFIDF(){
Map<String,Double> tfidf=new HashMap<String,Double>();
for(Map.Entry<String,Integer> me: TF.entrySet()){
String f=me.getKey();
double tf=1+Math.log(me.getValue());
double weight=tf*IDF.get(f);
tfidf.put(f, weight);
}
return tfidf;
}
/** * 進行規一化,每個特征除以這篇文本TFIDF值之和,構成新的TFIDF集 * @return filePath文件的特征-標准化TFIDF集 */
public Map<String,Double> getNormalTFIDF(){
Map<String,Double> tfidf=new HashMap<String,Double>();
Map<String,Double> weight=getTFIDF();
double sum=MathUtil.calSum(weight);//計算TFIDF總和
for(Map.Entry<String, Double> me: weight.entrySet()){
String f=me.getKey();
double w=me.getValue()/sum;
tfidf.put(f, w);
}
return MapUtil.descend(tfidf);
}
/** * 進行標准化,每個特征除以這篇文本中最大的TFIDF值,構成新的TFIDF集 * @return filePath文件的特征-標准化TFIDF集 */
public Map<String,Double> getStandardTFIDF(){
Map<String,Double> tfidf=new HashMap<String,Double>();
Map<String,Double> weight=getTFIDF();
Map<String,Double> temp=MapUtil.descend(weight);
Set<Map.Entry<String, Double>> set = temp.entrySet();
Iterator<Map.Entry<String,Double>> it = set.iterator();
double max=0;
if(it.hasNext()){
max=it.next().getValue();
}
for(Map.Entry<String, Double> me: weight.entrySet()){
String f=me.getKey();
double w=me.getValue()/max;
tfidf.put(f, w);
}
return MapUtil.descend(tfidf);
}
/** * 保存文本的TFIDF結果 * @param tf 文本的TF集 * @param idf IDF集 * @param savePath 保存路徑 */
public static void saveTFIDF(Map<String,Integer> tf,
Map<String,Double> idf,String savePath){
TFIDF tfidf=new TFIDF(tf,idf);
Map<String,Double> weight=tfidf.getStandardTFIDF();
Writer.saveMap(weight, savePath);
}
/** * 保存TFIDF結果 * @param filePath 文本集的TF集路徑 * @param idfPath IDF路徑 * @param tarPath 保存路徑 */
public static void saveTFIDF(String TFPath,String IDFPath,String tarPath){
File tar=new File(tarPath);
if(!tar.exists()) tar.mkdir();
Map<String,Double> idf=Reader.toDoubleMap(IDFPath);//IDF
File file=new File(TFPath);
File[] labels=file.listFiles();//類別
for(File label: labels){
String labelpath=tarPath+File.separator+label.getName();
File labelPath=new File(labelpath);
if(!labelPath.exists()) labelPath.mkdir();
File[] texts=label.listFiles();//文本
for(File text: texts){
String savePath=labelpath+File.separator+text.getName();
Map<String,Integer> tf=Reader.toIntMap(text.getAbsolutePath());
saveTFIDF(tf,idf,savePath);
System.out.println("Saved "+savePath);
}
}
}
public static void main(String args[]){
String TFPath="data\\r8trainTF";
String IDFPath="data\\r8trainIDF.txt";
String tarPath="data\\r8trainTFIDF3";
saveTFIDF(TFPath,IDFPath,tarPath);
}
}
向量空間模型VSM
余弦類似度
文本與文本之間的類似度不能簡單地用歐式距離來計算。更合理的計算方式是余弦類似度。
以下就是涉及這兩個知識點的工具類。
MathUtil存放通用的計算公式方法
import java.util.Map;
/** * * @author Angela */
public class MathUtil {
/** * 計算Map的鍵值之和 * @param map * @return */
public static double calSum(Map<String,Double> map){
double sum=0;
for(Map.Entry<String,Double> me: map.entrySet()){
sum+=me.getValue();
}
return sum;
}
/** * 計算兩篇文本的類似度 * @param text1 文本1 * @param text2 文本2 * @return text1和text2的余弦類似度。值越大越類似 */
public static double calSim(Map<String,Double> text1,
Map<String,Double> text2){
double sim=0;//類似度
double sum=0;//同樣特征的權重相乘之和
double len1=0;//文本1的長度
double len2=0;//文本2的長度
for(Map.Entry<String,Double> me: text1.entrySet()){
String f=me.getKey();
double value=me.getValue();
if(text2.containsKey(f)){
sum+=value*text2.get(f);
}
len1+=value*value;
}
for(Map.Entry<String,Double> me: text2.entrySet()){
double value=me.getValue();
len2+=value*value;
}
sim=sum/(Math.sqrt(len1)*Math.sqrt(len2));
return sim;
}
}
GetData獲取DF、TFIDF即VSM、類別成員clusterMember、類別列表LabelList及由TFIDF構造的其他數據
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** * * @author Angela */
public class GetData {
/** * 依據TFIDF得到相應的DF集合 * 假設你的TFIDF集僅僅是原始的一部分,就用這種方法來獲取相應的DF集 * @param tfidf TFIDF集合 * @return */
public static Map<String,Integer> getDF(Map<String,Map<String,Double>> tfidf){
Map<String,Integer> map=new HashMap<String,Integer>();
for(Map.Entry<String,Map<String,Double>> me: tfidf.entrySet()){
Map<String,Double> text=me.getValue();
for(Map.Entry<String,Double> t: text.entrySet()){
String feature=t.getKey();
if(map.containsKey(feature)){
map.put(feature,map.get(feature)+1);
}else{
map.put(feature, 1);
}
}
}
return map;
}
/** * 讀取文本集,返回Map<類別+文件名稱,Map<特征,權重>> * 當你進行特征選擇后,得到特征子集,能夠用這種方法 * 從文本集中構建新的VSM * @param filePath TFIDF存放路徑 * @param featureSet 特征集 * @return */
public static Map<String,Map<String,Double>> getTFIDF(String filePath,
Set<String> featureSet){
Map<String,Map<String,Double>> map=new HashMap<String,Map<String,Double>>();
File path=new File(filePath);
File[] files=path.listFiles();//類別
for(File file: files){
String label=file.getName();
File[] texts=file.listFiles();//文本
for(File text: texts){
Map<String,Double> tfidf=Reader.toDoubleMap(text.getAbsolutePath());
Map<String,Double> temp=new HashMap<String,Double>();
for(Map.Entry<String,Double> me: tfidf.entrySet()){
String feature=me.getKey();
if(featureSet.contains(feature)){
temp.put(feature,me.getValue());
}
}
map.put(label+File.separator+text.getName(), temp);
}
}
return map;
}
/** * 依據特征集構建新的VSM,返回Map<類別+文件名稱,Map<特征,權重>> * 當你進行特征選擇后,得到特征子集,能夠用這種方法 * 從原始的VSM中構建新的VSM * @param tfidf 原始的VSM * @param featureSet 特征集 * @return */
public static Map<String,Map<String,Double>> getTFIDF(
Map<String,Map<String,Double>> tfidf,Set<String> featureSet){
Map<String,Map<String,Double>> map=new HashMap<String,Map<String,Double>>();
for(Map.Entry<String,Map<String,Double>> me: tfidf.entrySet()){
Map<String,Double> text=me.getValue();
Map<String,Double> temp=new HashMap<String,Double>();
for(Map.Entry<String,Double> t: text.entrySet()){
String feature=t.getKey();
if(featureSet.contains(feature)){
temp.put(feature,t.getValue());
}
}
map.put(me.getKey(), temp);
}
return map;
}
/** * 依據TFIDF文本集和特征集構建 * Map<特征,Map<文本路徑名,特征在該文本中的TFIDF值>> * @param tfidf TFIDF文本集 * @param featureSet 特征集 * @return */
public static Map<String,Map<String,Double>> getTermIndex(
Map<String,Map<String,Double>> tfidf,Set<String> featureSet){
Map<String,Map<String,Double>> termIndex=
new HashMap<String,Map<String,Double>>();
for(String f: featureSet){//特征
//包括有特征f的全部文本及特征f在該文本中的權重
Map<String,Double> feature=new HashMap<String,Double>();
for(Map.Entry<String,Map<String,Double>> me: tfidf.entrySet()){
Map<String,Double> text=me.getValue();//文本
if(text.containsKey(f)){//假設文本包括特征f
//將文本的路徑和特征f的值賦給feature
feature.put(me.getKey(), text.get(f));
}
}
//將特征及feature賦給termIndex
termIndex.put(f,feature);
}
return termIndex;
}
/** * 依據文本特征集和特征集構建Map<特征,Set<文本路徑名>> * @param dataSet Map<文本,Set<特征>> * @param featureSet 特征集 * @return */
public static Map<String,Set<String>> getFeatureIndex(
Map<String,Set<String>> dataSet,Set<String> featureSet){
Map<String,Set<String>> featureIndex=
new HashMap<String,Set<String>>();
for(String f: featureSet){//特征
Set<String> textSet=new HashSet<String>();
for(Map.Entry<String,Set<String>> me: dataSet.entrySet()){
String textPath=me.getKey();//文本路徑
Set<String> feature=me.getValue();//文本的特征集
//假設文本包括特征f
if(feature.contains(f)){
textSet.add(textPath);
}
}
//將特征及文本集賦給termText
featureIndex.put(f,textSet);
}
return featureIndex;
}
/** * 文本集的類別成員 * @param filePath * @return */
public static Map<Integer,List<String>> getClusterMember(String filePath){
Map<Integer,List<String>> clusterMember=new HashMap<Integer,List<String>>();
File file=new File(filePath);
File[] labels=file.listFiles();
int labelNum=labels.length;
for(int i=0;i<labelNum;i++){
File[] texts=labels[i].listFiles();
String label=labels[i].getName();
List<String> member=new ArrayList<String>();
for(File text: texts){
member.add(label+File.separator+text.getName());
}
clusterMember.put(i, member);
}
return clusterMember;
}
/** * 類別集 * @param filePath * @return */
public static List<String> getLabelList(String filePath){
List<String> labelList=new ArrayList<String>();
File files=new File(filePath);
File[] file=files.listFiles();
for(File f: file){
labelList.add(f.getName());
}
return labelList;
}
/** * 依據TFIDF集構造類別集 * @param TFIDF * @return */
public static List<String> getLabelList(Map<String,Map<String,Double>> TFIDF){
Set<String> labels=new HashSet<String>();
for(Map.Entry<String,Map<String,Double>> me: TFIDF.entrySet()){
String path=me.getKey();
String label=path.substring(0,path.lastIndexOf(File.separator));
labels.add(label);
}
List<String> labelList=new ArrayList<String>(labels);
return labelList;
}
}
由於文本數據量一般非常大,並且VSM具有高維稀疏的特點。所以一般須要進行特征選擇。來降低特征的數量。
下一節,我將介紹幾種特征選擇方法。